← โปรเจกต์ทั้งหมด

Open Source · 2026

Thai Zip

พิมพ์ที่อยู่แค่บางส่วน ก็ได้ตำบล อำเภอ จังหวัด และรหัสไปรษณีย์ครบ เป็น library ค้นหาบน npm พร้อมเครื่องมือที่ช่วยเพิ่ม component React สำเร็จรูปลงโปรเจกต์

TypeScriptnpmReact
กำลังโหลดสาธิตที่อยู่ไทย

โจทย์

ใครเคยทำฟอร์ม checkout ของไทยน่าจะรู้ดีว่าช่องที่อยู่ปวดหัวแค่ไหน ตำบล อำเภอ จังหวัด รหัสไปรษณีย์ สี่ช่องนี้คนกรอกผิดกันตลอด

ผมทำงานด้าน e-commerce เลยต้องสร้างช่องค้นหาที่อยู่แบบนี้ซ้ำแล้วซ้ำอีก ส่วนที่ใช้ค้นหาเหมือนเดิมทุกครั้ง ต่างกันแค่หน้าตาในแต่ละโปรเจกต์

ผมเลยทำส่วนค้นหาให้ดีครั้งเดียว แล้วทำ UI ให้ปรับหน้าตาได้ง่าย ๆ

สิ่งที่ทำ

Thai Zip คือ npm package สองตัวที่ทำงานคู่กัน

thaiziplibrary สำหรับค้นหา พิมพ์ได้ทั้งไทยและอังกฤษ ค้นด้วยรหัสไปรษณีย์ได้ และดึงรายชื่อจังหวัด → อำเภอ → ตำบล ไปทำ dropdown เลือกทีละขั้นได้ ไม่ต้องติดตั้ง package อื่นเพิ่ม ใช้ได้ทั้งบน browser และ server
react-thaizipเครื่องมือแบบพิมพ์คำสั่งสำหรับ React และ Next.js ที่ copy component ที่อยู่สำเร็จรูปลงโปรเจกต์เป็นไฟล์โค้ดธรรมดา เราเลยแก้หน้าตาได้ตามใจ

มีข้อมูลที่อยู่มาให้ในตัว ได้แก่ 77 จังหวัด 928 อำเภอ 7,436 ตำบล และรหัสไปรษณีย์ 965 รหัส ข้อมูลมาจากโปรเจกต์ open source kongvut/thai-province-data (สัญญาอนุญาต MIT)

จังหวัด
77
อำเภอ
928
ตำบล
7,436
รหัสไปรษณีย์
965

เทคนิค

การค้นหาทำงานยังไง

ลองดูว่าเกิดอะไรขึ้นเมื่อมีคนพิมพ์ “ต.ลาดพร้าว”

  1. 01ทำความสะอาดคำค้น“ต.ลาดพร้าว” เหลือ “ลาดพราว”
  2. 02หั่นเป็นชิ้นละ 3 ตัวอักษรลาด · าดพ · ดพร · พรา · ราว
  3. 03ให้คะแนนที่อยู่ที่ตรงกันมีชิ้นที่พิมพ์มากี่เปอร์เซ็นต์
  4. 04เรียงลำดับคะแนนสูงขึ้นก่อน ถ้าเท่ากันดูว่าชื่อใกล้กว่า
  5. 05แสดงผลแนะนำลาดพร้าว › เขตลาดพร้าว · 10230

ทำความสะอาดคำค้น ตัดคำอย่าง จังหวัด อำเภอ ตำบล แขวง เขต และตัวย่ออย่าง จ. อ. ต. ออก รวมถึงวรรณยุกต์ด้วย พิมพ์ “ต.ลาดพร้าว” หรือ “ลาดพราว” (ไม่มีไม้โท) จึงเจอที่เดียวกัน

หั่นเป็นชิ้น ข้อความจะถูกหั่นเป็นชิ้นละ 3 ตัวอักษรที่ซ้อนกัน เรียกว่า trigram แล้วให้คะแนนแต่ละที่อยู่ตามสัดส่วนชิ้นที่ตรงกับคำที่พิมพ์ ต้องได้อย่างน้อย 40% ถึงจะแสดง เพราะคะแนนคิดจากชิ้น พิมพ์ไม่ครบนิดหน่อยก็ยังเจอ เช่น “ลาดพร้า” (ขาดตัวสุดท้าย) ก็ยังได้ลาดพร้าวเป็นอันดับแรก

เรียงลำดับ คะแนนสูงขึ้นก่อน ถ้าคะแนนเท่ากัน ที่อยู่ที่ชื่อตำบลตรงกับคำที่พิมพ์จะชนะ พิมพ์ “ลาดพร้าว” ตำบลลาดพร้าวจึงขึ้นก่อนจรเข้บัว ซึ่งแค่อยู่ในเขตเดียวกัน ที่ยังเท่ากันอยู่จะเรียงตามตัวอักษรไทย

ภาษาอังกฤษและรหัสไปรษณีย์

ตัวสะกดภาษาอังกฤษข้อมูลใช้ตัวสะกดทางการอย่าง “Lat Phrao” แต่คนมักพิมพ์ “ladprao” หรือ “lardprao” จึงมีรายการตัวสะกดยอดนิยมที่แปลงเป็นชื่อทางการให้ พิมพ์แบบไหนก็เจอที่ถูก
รหัสไปรษณีย์พิมพ์เป็นตัวเลขล้วนจะค้นด้วยรหัสไปรษณีย์ ถ้าครบ 5 หลักจะแสดงทุกตำบลที่ใช้รหัสนั้น เพราะมีถึง 230 รหัสที่ใช้ร่วมกันเกิน 10 ตำบล ผลลัพธ์จึงไม่ถูกตัดทิ้ง

โหลดเฉพาะที่จำเป็น

ข้อมูลโหลดเมื่อต้องใช้ข้อมูลที่อยู่ขนาดราว 120 KB (gzip) แยกเป็นไฟล์ต่างหาก จะดาวน์โหลดก็ต่อเมื่อโค้ดเรียก loadDefaultIndex() แล้วสร้าง index สำหรับค้นหาครั้งเดียว ใช้เวลาราว 30 ms
import เฉพาะที่ใช้thaizip คือส่วนค้นหา thaizip/react คือ React hook และ thaizip/data คือข้อมูลที่อยู่ แยก import กันได้ โปรเจกต์ที่ใช้ข้อมูลของตัวเองจึงไม่ต้องดาวน์โหลดข้อมูลชุดนี้เลย

การแยกข้อมูลออกจากการโหลดหน้าแรกไม่ได้เกิดขึ้นเอง ก่อน v0.7.5 เครื่องมือ build รวมข้อมูลไว้ในไฟล์เดียวกับตัวโหลด วัดใน Next.js app จริงพบว่าแค่ใส่ dropdown ตัวเดียว หน้าแรกก็หนักขึ้น 176.7 KB (gzip) ทั้งที่ผู้ใช้ยังไม่ได้แตะเลย พอแยกข้อมูลเป็นไฟล์ต่างหาก ตัวโหลดเหลือแค่ 2.3 KB

อีกจุดที่วัดแล้วแก้: เดิมเรียงชื่อภาษาไทยด้วย localeCompare("th") ซึ่งต้องตั้งค่ากฎการเรียงภาษาไทยใหม่ทุกครั้งที่เปรียบเทียบ เปลี่ยนมาสร้าง Intl.Collator ไว้ตัวเดียวแล้วใช้ซ้ำ การเรียงเร็วขึ้น 15–27 เท่า

ผลลัพธ์

ผลการค้นหาจริงจากเวอร์ชันปัจจุบัน

  • ลาดพร้าวลาดพร้าว › เขตลาดพร้าว › กรุงเทพมหานคร · 10230ภาษาไทย
  • ladpraoลาดพร้าว › เขตลาดพร้าว › กรุงเทพมหานคร · 10230ตัวสะกดอังกฤษที่คนนิยมพิมพ์
  • ต.ลาดพร้าวลาดพร้าว › เขตลาดพร้าว › กรุงเทพมหานคร · 10230มีคำนำหน้า
  • 10230ครบทั้ง 5 ตำบลที่ใช้รหัสนี้รหัสไปรษณีย์
  • 84320บ่อผุด › เกาะสมุย › สุราษฎร์ธานี · 84320ข้อมูลเกาะ แก้ใน v0.7.8

เพิ่ม component React ลงโปรเจกต์ได้ด้วยคำสั่งนี้

npm i thaizip
npx react-thaizip init
npx react-thaizip add autocomplete

init จะตรวจโปรเจกต์ให้ ทั้ง package manager, TypeScript, Tailwind และ shadcn/ui (Base UI, Radix หรือ React Aria) แล้วติดตั้ง thaizip ส่วน add จะ copy component ลงมา มีให้เลือก 5 แบบ คือ ช่องค้นหา, dropdown เลือกทีละขั้น, ฟอร์มที่อยู่เต็ม, ตัวแสดงที่อยู่ และ field สำหรับ react-hook-form

เวอร์ชันใหม่ออกอัตโนมัติจากข้อความ commit (release-please) และ publish ขึ้น npm ด้วย trusted publishing จึงไม่ต้องเก็บ token ของ npm ไว้ใน repo ตัว library มีเทสต์อัตโนมัติ 339 เคส รวมเทสต์ที่จะล้มทันทีถ้าลำดับผลค้นหาเปลี่ยนโดยไม่ตั้งใจ เดโมบนหน้านี้ใช้ thaizip เวอร์ชัน 0.7.8

สิ่งที่ได้เรียนรู้

ตรวจข้อมูล ไม่ใช่แค่โค้ดอำเภอ 9 แห่งและตำบล 24 แห่งที่ชื่อขึ้นต้นด้วย “เกาะ” หายไปจากข้อมูล รหัสอย่าง 84320 (เกาะสมุย) เลยค้นไม่เจอ v0.7.8 กู้กลับมาแล้ว และล็อกไฟล์ข้อมูลด้วย checksum ถ้าไฟล์ถูกแก้โดยไม่ตั้งใจ build จะล้มทันที
วัดก่อนค่อย optimizeสองจุดที่ได้ผลมากที่สุด คือข้อมูลที่ติดไปกับหน้าแรก และการเรียงชื่อไทยที่ช้า ทั้งคู่เจอจากการวัด ไม่ใช่การเดา

สิ่งสำคัญที่สุดที่ได้คือ แยกส่วนค้นหาให้เล็กและไม่ผูกกับ UI แล้วให้ UI เป็นโค้ดที่คนใช้เป็นเจ้าของเอง library ที่เทสต์มาดีตัวเดียวก็ใช้ได้กับฟอร์ม checkout ทุกแบบ ไม่ว่าจะหน้าตาแบบไหน