Open Source · 2026
Thai Zip
พิมพ์ที่อยู่แค่บางส่วน ก็ได้ตำบล อำเภอ จังหวัด และรหัสไปรษณีย์ครบ เป็น library ค้นหาบน npm พร้อมเครื่องมือที่ช่วยเพิ่ม component React สำเร็จรูปลงโปรเจกต์
โจทย์
ใครเคยทำฟอร์ม checkout ของไทยน่าจะรู้ดีว่าช่องที่อยู่ปวดหัวแค่ไหน ตำบล อำเภอ จังหวัด รหัสไปรษณีย์ สี่ช่องนี้คนกรอกผิดกันตลอด
ผมทำงานด้าน e-commerce เลยต้องสร้างช่องค้นหาที่อยู่แบบนี้ซ้ำแล้วซ้ำอีก ส่วนที่ใช้ค้นหาเหมือนเดิมทุกครั้ง ต่างกันแค่หน้าตาในแต่ละโปรเจกต์
ผมเลยทำส่วนค้นหาให้ดีครั้งเดียว แล้วทำ UI ให้ปรับหน้าตาได้ง่าย ๆ
สิ่งที่ทำ
Thai Zip คือ npm package สองตัวที่ทำงานคู่กัน
มีข้อมูลที่อยู่มาให้ในตัว ได้แก่ 77 จังหวัด 928 อำเภอ 7,436 ตำบล และรหัสไปรษณีย์ 965 รหัส ข้อมูลมาจากโปรเจกต์ open source kongvut/thai-province-data (สัญญาอนุญาต MIT)
- จังหวัด
- 77
- อำเภอ
- 928
- ตำบล
- 7,436
- รหัสไปรษณีย์
- 965
เทคนิค
การค้นหาทำงานยังไง
ลองดูว่าเกิดอะไรขึ้นเมื่อมีคนพิมพ์ “ต.ลาดพร้าว”
- 01ทำความสะอาดคำค้น“ต.ลาดพร้าว” เหลือ “ลาดพราว”
- 02หั่นเป็นชิ้นละ 3 ตัวอักษรลาด · าดพ · ดพร · พรา · ราว
- 03ให้คะแนนที่อยู่ที่ตรงกันมีชิ้นที่พิมพ์มากี่เปอร์เซ็นต์
- 04เรียงลำดับคะแนนสูงขึ้นก่อน ถ้าเท่ากันดูว่าชื่อใกล้กว่า
- 05แสดงผลแนะนำลาดพร้าว › เขตลาดพร้าว · 10230
ทำความสะอาดคำค้น ตัดคำอย่าง จังหวัด อำเภอ ตำบล แขวง เขต และตัวย่ออย่าง จ. อ. ต. ออก รวมถึงวรรณยุกต์ด้วย พิมพ์ “ต.ลาดพร้าว” หรือ “ลาดพราว” (ไม่มีไม้โท) จึงเจอที่เดียวกัน
หั่นเป็นชิ้น ข้อความจะถูกหั่นเป็นชิ้นละ 3 ตัวอักษรที่ซ้อนกัน เรียกว่า trigram แล้วให้คะแนนแต่ละที่อยู่ตามสัดส่วนชิ้นที่ตรงกับคำที่พิมพ์ ต้องได้อย่างน้อย 40% ถึงจะแสดง เพราะคะแนนคิดจากชิ้น พิมพ์ไม่ครบนิดหน่อยก็ยังเจอ เช่น “ลาดพร้า” (ขาดตัวสุดท้าย) ก็ยังได้ลาดพร้าวเป็นอันดับแรก
เรียงลำดับ คะแนนสูงขึ้นก่อน ถ้าคะแนนเท่ากัน ที่อยู่ที่ชื่อตำบลตรงกับคำที่พิมพ์จะชนะ พิมพ์ “ลาดพร้าว” ตำบลลาดพร้าวจึงขึ้นก่อนจรเข้บัว ซึ่งแค่อยู่ในเขตเดียวกัน ที่ยังเท่ากันอยู่จะเรียงตามตัวอักษรไทย
ภาษาอังกฤษและรหัสไปรษณีย์
โหลดเฉพาะที่จำเป็น
loadDefaultIndex() แล้วสร้าง index สำหรับค้นหาครั้งเดียว ใช้เวลาราว 30 msthaizip คือส่วนค้นหา 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
สิ่งที่ได้เรียนรู้
สิ่งสำคัญที่สุดที่ได้คือ แยกส่วนค้นหาให้เล็กและไม่ผูกกับ UI แล้วให้ UI เป็นโค้ดที่คนใช้เป็นเจ้าของเอง library ที่เทสต์มาดีตัวเดียวก็ใช้ได้กับฟอร์ม checkout ทุกแบบ ไม่ว่าจะหน้าตาแบบไหน