Vue 3 Composition API กับ Options API สำหรับไลบรารีคอมโพเนนต์ขนาดใหญ่
Vue 3 Composition API กับ Options API: ผลกระทบต่อการรียูส การทดสอบ และการเริ่มต้นสำหรับไลบรารีคอมโพเนนต์แอดมินขนาดใหญ่และทีมผู้ร่วมพัฒนา

ทำไมการเลือกนี้ถึงสำคัญในไลบรารีคอมโพเนนต์แอดมินขนาดใหญ่
ไลบรารีคอมโพเนนต์ขนาดใหญ่ในแอปแอดมินไม่ใช่เว็บไซต์การตลาดที่มีปุ่มไม่กี่ปุ่ม มันคือชุดบล็อกจำนวนมาก (เป็นสิบบล็อกหรือหลายร้อย) ที่วนซ้ำตามหน้าจอ: ตารางข้อมูลที่มีการจัดเรียงและการดำเนินการแบบกลุ่ม, แผงตัวกรอง, ฟอร์มที่มีกฎการตรวจสอบ, ลิ้นชักและโมดัล, กระบวนการยืนยัน และยูทิลิตี้เล็ก ๆ เช่นตัวเลือกวันที่และการตรวจสอบสิทธิ์
เพราะรูปแบบเหล่านี้ปรากฏบ่อย ทีมมักจะก็อปปี้และปรับโค้ดเพื่อให้ทันเวลา หนึ่งตารางได้แถบตัวกรองแบบกำหนดเอง อีกตารางได้แบบต่างเล็กน้อย และในไม่ช้าคุณจะมีห้าเวอร์ชันที่ “เกือบเหมือนกัน” นั่นคือจุดที่คำถาม Composition API vs Options API หยุดเป็นเรื่องรสนิยมส่วนตัวและเริ่มส่งผลต่อสุขภาพของทั้งไลบรารี
สิ่งที่มักพังเป็นอย่างแรกคือความสอดคล้อง UI ยังคงใช้งานได้ แต่พฤติกรรมเริ่มเบี้ยว: โมดัลปิดเมื่อกด Escape ในที่หนึ่งแต่ไม่ปิดในอีกที่ ฟิลด์เดียวกันตรวจสอบตอน blur ในหน้าหนึ่งและตรวจสอบตอน submit ในอีกหน้า หลังจากนั้นความเร็วในการพัฒนาก็ตกเพราะทุกการเปลี่ยนแปลงต้องตามล่า near-duplicates สุดท้ายความมั่นใจลดลง: คนหลีกเลี่ยงการรีแฟกเตอร์เพราะทำนายผลกระทบไม่ได้
การตัดสินใจเชิงปฏิบัติที่สำคัญมีสามเรื่อง:
- การนำโค้ดกลับมาใช้: คุณแพ็ก logic ร่วมอย่างไรโดยไม่ให้พึ่งพาซับซ้อนกัน
- การทดสอบ: ตรวจสอบพฤติกรรมได้ง่ายแค่ไหนโดยไม่ต้องพึ่งเทสต์หนักที่พึ่ง UI
- การเริ่มต้นผู้ร่วมพัฒนา: คนใหม่อ่านคอมโพเนนต์และทำการเปลี่ยนแปลงได้เร็วแค่ไหน
ตัวอย่างง่าย ๆ: แอดมินของคุณมีหน้ารายการ 20 หน้า และฝ่ายผลิตขอฟีเจอร์ “Saved filters” ใหม่ ถ้าตาราง ตัวกรอง และ logic ซิงก์กับ URL กระจัดกระจายและไม่สอดคล้อง คุณจะส่งของช้า หรือส่งของมากับบั๊ก สไตล์ API ที่คุณเลือกกำหนดว่า logic นั้นอยู่ในที่ที่นำกลับมาใช้ได้หรือไม่ มันเชื่อมต่อชัดแค่ไหนในแต่ละหน้า และใครใหม่สามารถขยายมันได้ง่ายแค่ไหน
ถ้าคุณกำลังก่อสร้างแอปแอดมิน Vue 3 (รวมทีมที่ใช้ Vue 3 ภายในแพลตฟอร์มอย่าง AppMaster สำหรับเลเยอร์ UI เว็บ) การตัดสินใจนี้ตั้งแต่ต้นจะช่วยประหยัดเวลาบำรุงรักษาหลายเดือนในภายหลัง
ความแตกต่างระหว่าง Options และ Composition ในโค้ดประจำวัน
วิธีที่รวดเร็วที่สุดในการรู้สึกความต่างคือเปิดคอมโพเนนต์แอดมินขนาดใหญ่และถามว่า: “ฉันจะเปลี่ยนพฤติกรรมของฟีเจอร์นี้ได้ที่ไหน?” ในไลบรารีคอมโพเนนต์ คำถามนี้จะเกิดขึ้นทุกวัน
กับ Options API โค้ดถูกจัดกลุ่มตามประเภท: data สำหรับสถานะ, methods สำหรับการกระทำ, computed สำหรับค่าที่ได้มา และ watch สำหรับผลข้างเคียง โครงสร้างนี้อ่านง่ายเมื่อคอมโพเนนต์เล็ก ในคอมโพเนนต์ตารางหรือฟอร์มขนาดใหญ่มักจะเห็น logic ของฟีเจอร์เดียว (เช่น การดำเนินการแบบกลุ่มหรือการตรวจสอบฟิลด์) กระจายอยู่ในหลายบล็อก คุณจะจัดให้เรียบร้อยได้ แต่มันต้องมีวินัยและการตั้งชื่อที่สม่ำเสมอเพื่อหลีกเลี่ยงการต้อง “เด้งไปมาระหว่างไฟล์”
กับ Composition API โค้ดมักจัดกลุ่มตามฟีเจอร์ คุณกำหนดสถานะที่เกี่ยวข้อง ค่าที่ได้มา ผลข้างเคียง และตัวช่วยไว้ใกล้กัน และคุณสามารถย้าย logic ที่ซ้ำไปเป็น composable ได้ ในไลบรารีสไตล์แอดมิน สิ่งนี้มักตรงกับวิธีคิดของคน: “ทุกอย่างเกี่ยวกับการกรองอยู่ที่นี่”, “ทุกอย่างเกี่ยวกับการเลือกแถวอยู่ที่นี่” มันยังช่วยลดการซ้ำระหว่างคอมโพเนนต์ที่คล้ายกัน เช่น นำ usePagination กลับมาใช้ในหลายตาราง
ความต่างสำคัญอีกอย่างคือลักษณะของการพึ่งพา
- Options API อาจรู้สึกแฝง: เมธอดอาจพึ่งพา
this.user,this.filters, และthis.loadingซึ่งคุณจะรู้ได้ก็ต่อเมื่ออ่านข้อความภายในเมธอด - Composition API มักชัดเจนกว่า: เมื่อฟังก์ชันปิดทับ
filtersและloadingคุณจะเห็นตัวแปรเหล่านั้นกำหนดไว้ใกล้ ๆ และสามารถส่งพวกมันเข้าไปยังฟังก์ชันช่วยเหลือเมื่อจำเป็น
ข้อเสียคือ Composition API อาจเสียงดังได้ถ้าทุกอย่างถูกเทรวมเข้าไปใน setup() เดียวโดยไม่มีโครงสร้าง
กฎปฎิบัติแบบง่าย ๆ:
- เลือก Options API เมื่อคอมโพเนนต์เป็นงานนำเสนอเป็นหลักและมี logic เบา
- เลือก Composition API เมื่อคอมโพเนนต์มีหลายฟีเจอร์ที่มีกฎร่วมกันทั่วไลบรารี
- ถ้าใช้ Composition API ให้ตกลงรูปแบบง่าย ๆ ที่จัดกลุ่มโค้ดตามฟีเจอร์ (ไม่ใช่ “ใส่ refs ทั้งหมดก่อน”)
- ถ้าใช้ Options API ให้บังคับการตั้งชื่อและเก็บ logic ที่เกี่ยวข้องไว้ด้วยกันพร้อมคอมเมนต์สั้น ๆ และชื่ิอเมธอดที่สม่ำเสมอ
ทั้งสองแบบทำงานได้ จุดสำคัญคือเลือกสไตล์การจัดองค์กรที่ทำให้การเปลี่ยนแปลงครั้งต่อไปชัดเจน ไม่ใช่ฉลาดล้ำ
การนำโค้ดกลับมาใช้: อะไรที่ขยายได้สะอาด และอะไรที่จะยุ่งเหยิง
ในแอปสไตล์แอดมิน การนำกลับมาใช้ไม่ใช่ฟีเจอร์เสริม คุณทำพฤติกรรมเดิมซ้ำหลายสิบหน้า และความไม่สอดคล้องเล็ก ๆ จะแปลงเป็นบั๊กและตั๋วซัพพอร์ต
การนำกลับมาใช้ส่วนใหญ่ตกอยู่ในหมวดที่เกิดซ้ำ: การจัดเรียง/กรอง/แบ่งหน้าให้ตรงกับ backend, การตรวจสอบฟอร์มและแมปข้อผิดพลาด, การเช็กสิทธิ์และการปิด UI, การซิงก์คิวรี (พารามิเตอร์ URL, มุมมองที่บันทึก, ตัวกรองเริ่มต้น), และการดำเนินการแบบกลุ่มด้วยกฎการเลือกแถว
การนำกลับมาใช้กับ Options API: ทรงพลังแต่ซ่อนความซับซ้อนได้ง่าย
กับ Options API การรียูสมักเริ่มด้วย mixins, extends, หรือ plugins
Mixins เร็วแต่ขยายไม่ดีเพราะซ่อนแหล่งที่มาของเมธอดหรือ computed สอง mixin อาจชนกันที่ชื่อเมธอด และคุณจะดีบักพฤติกรรมที่มองไม่เห็นในไฟล์คอมโพเนนต์
extends อาจดูสะอาดกว่ามิกซ์อิน แต่ยังสร้างปริศนาการสืบทอดที่คุณต้องอ่านหลายไฟล์เพื่อเข้าใจว่าคอมโพเนนต์ทำอะไร Plugins เหมาะกับเรื่องระดับแอป (directive ทั่วไป, บริการร่วม) แต่ไม่ใช่ที่ที่ดีสำหรับกฎธุรกิจที่เปลี่ยนตามหน้าจอ
จุดที่ยุ่งมักมาถึงเมื่อการรียูสกลายเป็นแบบแฝง ผู้ร่วมพัฒนาคนใหม่ตอบไม่ได้ว่า “ข้อมูลนี้มาจากไหน?” โดยไม่ต้องค้นทั้งโค้ดเบส
การนำกลับมาใช้กับ Composition API: composable ที่ชัดเจน
การนำกลับมาใช้กับ Composition API มักสร้างขึ้นรอบ ๆ composable: ฟังก์ชันเล็ก ๆ ที่คืน refs, computed และ handlers ข้อดีใหญ่คือการรียูสปรากฏชัดใกล้ ๆ ส่วนบนของคอมโพเนนต์ และคุณสามารถส่งพารามิเตอร์ แทนการพึ่งพา context ที่ซ่อนอยู่
ตัวอย่างเช่น usePagination สามารถรับค่าเริ่มต้นและส่งเหตุการณ์การเปลี่ยนในรูปแบบที่สอดคล้อง ในขณะที่ usePermissions สามารถรับบทบาทปัจจุบันและชื่อฟีเจอร์ ณ จุดนั้น การตัดสินใจจะเป็นเรื่องของว่าห้องสมุดของคุณชอบการต่อสายที่ชัดเจนหรือการสืบทอดที่แฝง
เพื่อให้การรียูสคาดเดาได้ ให้จัดการแต่ละหน่วยที่นำกลับมาใช้เหมือน API เล็ก ๆ: ตั้งชื่อชัดเจน กำหนดอินพุตและเอาต์พุต และรับผิดชอบเพียงอย่างเดียว หาก composable เริ่มจัดการ pagination, caching, permissions และ notifications ให้แยกมัน จะง่ายกว่ามากที่จะสลับชิ้นส่วนหนึ่งโดยไม่ทำลายส่วนอื่น
สร้างคอมโพเนนต์ฟอร์มและตารางที่นำกลับมาใช้ได้โดยไม่เจ็บปวด
ในแอปสไตล์แอดมิน ฟอร์มและตารางคือที่ที่ไลบรารีคอมโพเนนต์จะคุ้มค่าหรือกลายเป็นเขาวงกต ทั้งสอง API ทำงานได้ ความต่างคือคุณแพ็กพฤติกรรมร่วมอย่างไร เช่น สถานะ dirty, แมปข้อผิดพลาด, และ flow การ submit โดยไม่ทำให้คอมโพเนนต์ทุกตัวรู้สึก “พิเศษ”
สำหรับ logic ฟอร์มร่วม Options API มักดันให้คุณใช้มิกซ์อินหรือ helper ร่วม มิกซ์อินอาจสะดวกตอนแรก แต่ต่อมาจะยากที่จะตอบคำถามพื้นฐาน: “ข้อผิดพลาดของฟิลด์นี้มาจากไหน?” หรือ “ทำไมปุ่มส่งถูกปิด?”
Composition API ทำให้รียูสประเภทนี้มองเห็นได้เพราะคุณย้าย logic ไปเป็น composable (เช่น useDirtyState, useFormErrors, useSubmitFlow) และเห็นชัดเจนว่าคอมโพเนนต์ฟอร์มดึงอะไรเข้ามาบ้าง ในไลบรารีขนาดใหญ่ ความชัดเจนนี้มักมีค่ายิ่งกว่าการประหยัดบรรทัดโค้ด
วิธีปฏิบัติที่จะรักษา API คงที่คือปฏิบัติต่อผิวสาธารณะเป็นสัญญา: props, emits, และ slots ควรเปลี่ยนน้อย ถึงแม้จะเขียนซ้ำภายใน สัญญานี้เหมือนกันทั้งสองสไตล์ แต่ Composition API มักทำให้รีแฟกเตอร์ปลอดภัยกว่าเพราะคุณสามารถแทนที่ composable หนึ่งครั้งทีละตัวโดยไม่แตะ template API
รูปแบบที่มักยังคงมีเหตุผลเมื่อไลบรารีเติบโต:
- สร้างคอมโพเนนต์ฐานที่ทำงานหนึ่งอย่างได้ดี (BaseInput, BaseSelect, BaseTable) แล้วประกอบเป็นคอมโพเนนต์ฟีเจอร์
- ใช้ slots สำหรับความยืดหยุ่นของเลย์เอาต์ แทนการเพิ่ม props สำหรับทุกกรณีพิเศษ
- ทำให้อีเวนต์เป็นมาตรฐานตั้งแต่ต้น (เช่น
update:modelValue,submit,rowClick) เพื่อให้แอปไม่พึ่งรายละเอียดภายใน - เก็บการตรวจสอบและการฟอร์แมตใกล้กับอินพุต แต่เก็บกฎธุรกิจด้านนอก (ใน composable หรือ parent)
การโอเวอร์-แอบสแตรกชันเป็นกับดักทั่วไป คอมโพเนนต์ “super form” ที่จัดการทุกชนิดฟิลด์ ทุกกฎการตรวจสอบ และทุกตัวเลือกเลย์เอาต์มักจะใช้งานยากกว่าการใช้ Vue ธรรมดา กฎที่ดีคือ: ถ้าคอมโพเนนต์ฐานต้องมี props มากกว่าจำนวนจำกัดเพื่อครอบความต้องการของทีมทั้งหลาย มันอาจจะควรเป็นสองคอมโพเนนต์
บางครั้งการทำซ้ำเล็ก ๆ ก็เป็นคำตอบที่ถูกต้อง ถ้าเพียงหน้าเดียวต้องการ header ตารางประหลาด ๆ แบบ multi-row grouping ก็ให้ก็อปปี้ชิ้นเล็ก ๆ และเก็บไว้ท้องถิ่น การออกแบบฉลาดจนเกินไปมีหางการบำรุงรักษายาว โดยเฉพาะเมื่อผู้ร่วมพัฒนาใหม่เข้ามาและพยายามเข้าใจความต่างระหว่างคอมโพเนนต์ “ปกติ” กับกรอบงานภายในกรอบงาน
ถ้าคุณตัดสินใจระหว่าง Composition และ Options สำหรับไลบรารีฟอร์มและตารางขนาดใหญ่ ให้เพิ่มความสำคัญกับความอ่านง่ายของเส้นทางข้อมูลก่อน การรียูสดี แต่ไม่ควรซ่อนเส้นทางจากการกระทำของผู้ใช้ไปยังเหตุการณ์ที่ถูกปล่อยออกมา
ผลกระทบต่อการทดสอบ: อะไรที่ตรวจสอบได้ง่ายขึ้น
ในไลบรารีคอมโพเนนต์ เทสต์มักแบ่งเป็นสามกลุ่ม: logic บริสุทธิ์ (การฟอร์แมต, การตรวจสอบ, การกรอง), การเรนเดอร์ (แสดงอะไรสำหรับสถานะที่กำหนด), และการโต้ตอบ (คลิก, input, คีย์บอร์ด, emits) สไตล์ API ที่คุณเลือกเปลี่ยนว่าคุณจะทดสอบกลุ่มแรกได้บ่อยแค่ไหนโดยไม่ต้อง mount คอมโพเนนต์ทั้งหมด
เทสต์ Options API มักเป็นแบบ “mount คอมโพเนนต์ ปรับสถานะของอินสแตนซ์ แล้ว assert DOM” แบบนั้นใช้งานได้ แต่จะผลักให้เทสต์ใหญ่ขึ้นเพราะ logic ผสมอยู่ใน methods, computed, watch, และ lifecycle hooks เมื่อมีข้อผิดพลาด คุณก็ต้องใช้เวลาแยกว่าเป็นปัญหา timing ของ watcher, ผลข้างเคียงของ lifecycle หรือ logic เอง
Options API มักเหมาะกับ:
- ฟลูวผู้ใช้ที่พึ่งลำดับของ lifecycle (fetch ตอน mount, reset ตอนเปลี่ยน route)
- พฤติกรรมขับเคลื่อนด้วย watcher (auto-save, query syncing)
- การส่งอีเวนต์จากเมธอดของคอมโพเนนต์ (
save(),reset(),applyFilter())
Composition API เปลี่ยนสมดุล ถ้าคุณย้าย logic ไปเป็น composable คุณสามารถ unit test logic นั้นเป็นฟังก์ชันธรรมดา ด้วยอินพุตเล็ก ๆ และเอาต์พุตชัดเจน ซึ่งลดจำนวนเทสต์ที่ต้อง mount และทำให้ข้อผิดพลาดเกิดในพื้นที่เล็กลง มันยังทำให้การควบคุมการพึ่งพาง่ายขึ้น: แทนการม็อก global คุณส่งการพึ่งพา (เช่น ฟังก์ชัน fetch, ตัวฟอร์แมตวันที่, หรือตัวเช็กสิทธิ์) เข้าไปใน composable
ตัวอย่าง konkret: AdminTable ที่รียูสได้มีการจัดเรียง การแบ่งหน้า และแถวที่เลือก ใน Composition API logic การเลือกแถวอาจอยู่ใน useRowSelection() และถูกทดสอบได้โดยไม่ต้องเรนเดอร์ตารางเลย (toggle, clear, select all, เก็บข้ามหน้า) จากนั้นเก็บเทสต์คอมโพเนนต์น้อยลงเพื่อยืนยันว่าเทมเพลตเชื่อมปุ่ม เช็กบ็อกซ์ และอีเวนต์อย่างถูกต้อง
เพื่อให้เทสต์เล็กและอ่านง่าย (ไม่ว่าจะสไตล์ไหน) สร้างรอยต่อระหว่าง logic และ UI ที่ชัดเจน:
- ใส่กฎธุรกิจในฟังก์ชันบริสุทธิ์หรือ composable ไม่ใช่ใน watcher
- เก็บผลข้างเคียง (fetch, storage, timers) ไว้หลังการพึ่งพาที่ฉีดเข้าไป
- เลือก integration test โฟกัสเล่มละไม่กี่ชิ้นต่อคอมโพเนนต์ แทนที่จะทดสอบ “ทุกอย่าง” ในการทดสอบเดียว
- ตั้งชื่อ states และ events ให้สอดคล้องทั่วไลบรารี (ลดการตั้งค่าเทสต์)
- หลีกเลี่ยงการพึ่งพาที่ซ่อนอยู่ (เช่น เมธอด A พึ่งพาให้ watcher B รัน)
ถ้าจุดมุ่งหมายของคุณคือการตัดสินใจสไตล์ที่ทำให้เทสต์เสถียร ให้ผลักไปทางพฤติกรรมที่ขับเคลื่อนด้วย lifecycle น้อยลงและหน่วย logic ที่แยกได้มากขึ้นซึ่งตรวจสอบได้โดยไม่ต้องพึ่ง DOM
การเริ่มต้นผู้ร่วมพัฒนาใหม่: คนจะผลิตผลงานได้เร็วแค่ไหน
ในไลบรารีคอมโพเนนต์แอดมินขนาดใหญ่ การเริ่มต้นไม่ได้เกี่ยวกับการสอน Vue มากเท่าเรื่องช่วยให้คนหาของ พบ conventions เดียวกัน และรู้สึกปลอดภัยที่จะเปลี่ยนแปลง ส่วนใหญ่ของความช้าคือช่องว่างสามอย่าง: การนำทาง (logic อยู่ที่ไหน?), conventions (ทำแบบนี้ยังไงที่นี่?), และความมั่นใจ (ฉันจะเปลี่ยนสิ่งนี้โดยไม่ทำให้ห้าหน้าพังได้อย่างไร?)
กับ Options API ผู้มาใหม่มักเริ่มได้เร็วในวันแรกเพราะโครงสร้างคุ้นเคย: props, data, computed, methods, watchers ข้อเสียคือพฤติกรรมจริงมักกระจาย ฟีเจอร์เดียวเช่น “การกรองฝั่งเซิร์ฟเวอร์” อาจถูกแยกไว้ใน watcher, computed, สองเมธอด และมิกซ์อิน คนจะอ่านแต่ละบล็อกได้ แต่ต้องใช้เวลาเย็บเรื่องราวเข้าด้วยกัน
กับ Composition API ประโยชน์ของการเริ่มต้นคือ logic ที่เกี่ยวข้องสามารถอยู่รวมกัน: สถานะ ผลข้างเคียง และตัวช่วยในที่เดียว ค่าใช้จ่ายคือความคุ้นเคยกับ composable ผู้ร่วมพัฒนาต้องเข้าใจรูปแบบเช่น useTableState() และการไหลของค่าที่ reactive ผ่าน composable หลายตัว ถ้าไม่มีขอบเขตชัดเจน มันอาจเหมือนการกระโดดไปมาระหว่างไฟล์โดยไม่มีแผนที่
คอนเวนชันบางอย่างจะลดคำถามส่วนใหญ่ได้ ไม่ว่าจะเลือกสไตล์ใด:
- ใช้โครงสร้างที่คาดเดาได้:
components/,composables/,types/,tests/ - เลือกรูปแบบการตั้งชื่อและยึดมั่นกับมัน (ตัวอย่าง:
useX,XTable,XForm) - เพิ่ม docblock สั้น ๆ: คอมโพเนนต์ทำอะไร, props สำคัญ, และอีเวนต์หลัก
- กำหนดกฎ “escape hatch” เดียว (เมื่อไหร่ที่อนุญาตให้เพิ่ม composable หรือ helper ใหม่)
- เก็บคอมโพเนนต์ “golden” เล็ก ๆ ที่สาธิต pattern ที่ต้องการ
ตัวอย่าง: ถ้าทีมของคุณสร้างแผงแอดมิน Vue 3 แล้วปรับแต่งมัน (เช่น เว็บแอปที่สร้างบน AppMaster แล้วนักพัฒนาขยาย) การเริ่มต้นจะดีขึ้นมากเมื่อมีที่เดียวชัดเจนที่จะปรับพฤติกรรมตาราง (sorting, filters, pagination) และที่เดียวชัดเจนที่จะปรับการเชื่อม UI (slots, column renderers, row actions) ความชัดเจนนี้สำคัญกว่าสไตล์ API ที่คุณเลือก
ขั้นตอนทีละขั้น: เลือกสไตล์และแนะนำอย่างปลอดภัย
สำหรับไลบรารี UI แอดมินขนาดใหญ่ วิธีปลอดภัยที่สุดคืเริ่มจากฟีเจอร์หนึ่งที่มีขอบเขตชัดเจนและปฏิบัติต่อมันเหมือน pilot ไม่ใช่รีไรท์ทั้งหมด
เลือกโมดูลเดียวที่มีพฤติกรรมชัดเจนและรียูสสูง เช่น การกรองตารางหรือการตรวจสอบฟอร์ม ก่อนเปลี่ยนโค้ด ให้จดว่ามันทำอะไรตอนนี้: อินพุต (props, query params, การกระทำผู้ใช้), เอาต์พุต (events, emits, การเปลี่ยน URL), และขอบเขตพิเศษ (empty state, reset, ข้อผิดพลาดจากเซิร์ฟเวอร์)
ถัดไป กำหนดขอบเขต ตัดสินใจว่าสิ่งใดต้องอยู่ภายในคอมโพเนนต์ (การเรนเดอร์, DOM events, รายละเอียดการเข้าถึง) และสิ่งใดย้ายไปโค้ดร่วมได้ (การแยกพารามิเตอร์, debouncing, สร้างพารามิเตอร์ API, สถานะเริ่มต้น) นี่คือที่ที่ไลบรารีมักผิดพลาด: ถ้าคุณย้ายการตัดสินใจ UI เข้าไปในโค้ดร่วม คุณจะทำให้มันยากต่อการรียูส
แผนการเปิดตัวเชิงปฏิบัติ:
- เลือกคอมโพเนนต์หนึ่งที่โชว์ pattern ชัดเจนและถูกใช้หลายหน้าจอ
- สกัดหน่วยร่วมหนึ่งตัว (composable หรือ helper ธรรมดา) ที่มี API เล็กและชัดเจน
- เพิ่มเทสต์มุ่งเป้าให้หน่วยนั้นก่อน ตามสถานการณ์จริงของแอดมิน
- รีแฟกเตอร์คอมโพเนนต์ที่เลือกแบบ end-to-end โดยใช้หน่วยใหม่
- นำ pattern เดียวกันไปใช้ในคอมโพเนนต์อีกตัวเพื่อยืนยันว่า scale ได้
ให้ API ร่วมมีความธรรมดาและชัดเจน เช่น useTableFilters() อาจรับ filters เริ่มต้นและเปิด filters, apply(), reset(), และ toRequestParams() หลีกเลี่ยง “เวทมนตร์” ที่อ่านจาก global state เว้นแต่ว่านั่นเป็นกฎแน่นอนของแอปคุณ
หลัง pilot ให้เผยแพร่แนวทางภายในสั้น ๆ พร้อมตัวอย่างที่ผู้ร่วมพัฒนาสามารถก็อปปี้ กฎที่ชัดเจนข้อเดียวย่อมดีกว่าเอกสารยาว เช่น: “ทุก logic การกรองตารางอยู่ใน composable; คอมโพเนนต์ผูก UI controls และเรียก apply() เท่านั้น”
ก่อนขยายแนวทาง ใช้มาตรฐานคำว่าเสร็จ (definition of done) ง่าย ๆ:
- โค้ดใหม่อ่านเหมือนกันในสองคอมโพเนนต์ต่างกัน
- เทสต์ครอบคลุม logic ร่วมโดยไม่ต้อง mount UI เต็ม
- ผู้ร่วมพัฒนาใหม่สามารถเปลี่ยนกฎการกรองโดยไม่แตะไฟล์ที่ไม่เกี่ยวข้อง
ถ้าทีมของคุณยังสร้างพอร์ทัลแอดมินด้วยเครื่องมือ no-code อย่าง AppMaster ให้ใช้แนวคิด pilot เดียวกัน: เลือกเวิร์กโฟลว์หนึ่ง (เช่น การอนุมัติ), กำหนดพฤติกรรม, แล้วทำให้ pattern เป็นมาตรฐานก่อนจะขยาย
ข้อผิดพลาดและกับดักทั่วไปในไลบรารีขนาดใหญ่
ปัญหาใหญ่ในไลบรารีคอมโพเนนต์ขนาดใหญ่ไม่ค่อยเกี่ยวกับไวยากรณ์ แต่เกิดจากการตัดสินใจท้องถิ่นเล็ก ๆ ที่สะสมและทำให้การรียูส การทดสอบ และการดูแลยากขึ้น
กับดักทั่วไปคือการผสมรูปแบบแบบสุ่ม ถ้าครึ่งไลบรารีใช้ Options API และอีกครึ่งใช้ Composition API โดยไม่มีนโยบาย ทุกคอมโพเนนต์ใหม่จะกลายเป็นประเด็นถกเถียง นอกจากนี้คุณยังได้โซลูชันซ้ำ ๆ สำหรับปัญหาเดียวกันแต่เขียนคนละรูปแบบ หากอนุญาตทั้งสองแบบ ให้เขียนนโยบายชัด: คอมโพเนนต์ใหม่ใช้สไตล์หนึ่ง, โค้ดเก่าแตะเฉพาะเมื่อจำเป็น, และ logic ร่วมอยู่ที่เดียวที่ตกลงกัน
กับดักอีกอย่างคือ “god composable” เริ่มจาก useAdminPage() หรือ useTable() ที่ช่วยได้ในตอนแรก แล้วค่อย ๆ ดูด routing, fetching, caching, selection, dialogs, toasts, และ permissions เข้ามา มันยากทดสอบเพราะการเรียกเดียวกระตุ้นผลข้างเคียงหลายอย่าง และยากรียูสเพราะแต่ละหน้าต้องการแค่ส่วนน้อยแต่แบกความซับซ้อนทั้งหมด
Watcher ก็เป็นแหล่งปัญหา พวกมันเพิ่มง่ายเมื่อบางอย่างไม่ตรงกัน แต่บั๊กเรื่อง timing จะปรากฏทีหลัง โดยเฉพาะกับ async data และ input ที่ debounce เมื่อคนรายงานว่า “บางครั้งมันล้างการเลือกของฉัน” คุณอาจต้องใช้เวลาหลายชั่วโมงเพื่อติดตามซ้ำ
สัญญาณแดงที่มักบอกว่าไลบรารีกำลังมีปัญหา:
- คอมโพเนนต์ทำงานได้ก็ต่อเมื่อใช้งานตามลำดับ props และ events เฉพาะ
- composable อ่าน/เขียน global state โดยไม่ชัดเจน
- watcher หลายตัวอัปเดตสถานะเดียวกัน
- รีแฟกเตอร์ทำให้หน้าคอนซูเมอร์พังเล็ก ๆ อยู่บ่อยครั้ง
- ผู้ร่วมพัฒนาหลีกเลี่ยงการแตะ "ไฟล์นั้น" เพราะรู้สึกเสี่ยง
กับดักสุดท้ายคือการทำลาย public API ขณะรีแฟกเตอร์ ในแอปแอดมิน คอมโพเนนต์อย่างตาราง ตัวกรอง และฟิลด์แบบกระจายเร็ว การเปลี่ยนชื่อ prop, เปลี่ยนอีเวนต์ที่ปล่อย, หรือลดพฤติกรรมของ slot อาจทำให้หน้าจอหลายสิบหน้าพังโดยเงียบ ๆ
วิธีที่ปลอดภัยกว่าคือปฏิบัติต่อ API คอมโพเนนต์เหมือนสัญญา: ค่อย ๆ ยกเลิกการใช้งาน (deprecate) แทนลบ, เก็บ compatibility shims ไว้สักระยะ, และเพิ่มเทสต์การใช้งานง่ายที่ mount คอมโพเนนต์ในแบบที่ผู้ใช้ใช้งานจริง หากคุณสร้างอินเตอร์เฟซแอดมิน Vue 3 ที่ถูกสร้างโดยเครื่องมือเช่น AppMaster ความสำคัญนี้จะเพิ่มขึ้น เพราะสัญญาคอมโพเนนต์ที่สม่ำเสมอทำให้การนำหน้าจอกลับมาใช้และการเปลี่ยนแปลงคาดเดาได้
การตรวจสอบด่วนก่อนตัดสินใจ
ก่อนเลือก Composition API, Options API, หรือผสม ให้ทำการตรวจสอบอย่างรวดเร็วกับคอมโพเนนต์จริงจากไลบรารีของคุณ เป้าหมายง่าย ๆ: ทำให้ห logic เจอได้ง่าย รียูสอย่างปลอดภัย และทดสอบส่วนที่แอดมินพึ่งพาได้
1) คนหาพบ logic ได้เร็วหรือไม่?
เปิดคอมโพเนนต์ตัวอย่างที่หนักแอดมิน (filters + table + permissions + bulk actions) สมมติว่าคุณเป็นคนใหม่ในโค้ดเบส
สัญญาณที่ดีคือคนตอบได้ว่า “logic ตัวกรองอยู่ที่ไหน?” หรือ “อะไรเป็นตัวตัดสินว่าปุ่มถูกปิด?” ในไม่เกิน 2 นาที กับ Options API นี่มักหมายถึง logic ถูกแบ่งชัดใน computed, methods, และ watchers กับ Composition API นี่หมายถึง setup() ถูกจัดเป็นบล็อกเล็ก ๆ ที่มีชื่อ หรือใช้ composable และหลีกเลี่ยงฟังก์ชันยักษ์เดียว
2) ยูทิลิตี้ร่วมทำงานเหมือนฟังก์ชัน ไม่ใช่เวทมนตร์หรือไม่?
โค้ดร่วมควรมีอินพุตและเอาต์พุตชัดเจน และผลข้างเคียงน้อย ถ้า helper เข้าถึง global state, แก้ไขออบเจ็กต์ที่ส่งเข้ามา, หรือเรียกเครือข่ายโดยไม่ชัดเจน การรียูสจะไม่ปลอดภัย
เช็คลิสต์ด่วน:
- คุณอ่าน signature ของ composable/helper แล้วเดาได้หรือไม่ว่ามันคืนค่าอะไร?
- คุณใช้มันในสองคอมโพเนนต์โดยไม่ต้องตั้งค่าซ่อนเร้นไหม?
- คุณรีเซ็ตสถานะมันในเทสต์ได้โดยไม่ต้องใช้การบิดเบี้ยวไหม?
3) เทสต์ของคุณมุ่งไปยังพฤติกรรมแอดมินหรือไม่?
แอปแอดมินล้มเหลวแบบคาดเดาได้: ตัวกรองใช้ไม่ถูกต้อง, สิทธิ์รั่ว, ฟอร์มตรวจสอบไม่สอดคล้อง, และสถานะตารางพังหลังแก้ไข
แทนที่จะทดสอบรายละเอียดการทำงานภายใน (watchers vs refs) ให้เขียนเทสต์ตามพฤติกรรม: “ให้ role X, action Y ถูกซ่อน”, “การบันทึกแสดงข้อผิดพลาดและเก็บข้อมูลผู้ใช้”, “การเปลี่ยนตัวกรองอัปเดตคิวรีและข้อความ empty state” วิธีนี้ทำให้เทสต์เสถียรแม้คุณจะรีแฟกเตอร์ในภายหลัง
4) คุณมีมาตรฐานสำหรับสถานะ async หรือไม่?
ไลบรารีใหญ่จะมี async flow เล็ก ๆ เยอะ: โหลด options, ตรวจสอบฟิลด์, ดึงแถวตาราง, retry on failure ถ้าทุกคอมโพเนนต์คิดรูปแบบ loading/error ของตัวเอง การเริ่มต้นและดีบักจะช้า
เลือกรูปร่างสถานะ async ชัดเจนหนึ่งแบบ (loading, error, retries, cancellation) Composition API มักสนับสนุน useAsyncX() ที่รียูสได้ ในขณะที่ Options API สามารถมาตรฐาน data() state และ shared methods ได้ ทั้งสองได้ถ้าใช้สม่ำเสมอ
5) API สาธารณะของคอมโพเนนต์ชัดเจนและคงที่หรือไม่?
ปฏิบัติต่อคอมโพเนนต์เหมือนผลิตภัณฑ์ props, emitted events, และ slots คือสัญญา ถ้าสัญญานี้เปลี่ยนบ่อย ทุกหน้าจะเปราะบาง มองหาคอมเมนต์ที่อธิบายเจตนา (ไม่ใช่กลไก): props หมายถึงอะไร, อีเวนต์ไหนรับประกัน, และอะไรถือเป็นภายใน ถ้าคุณสร้างเครื่องมือภายในด้วยแพลตฟอร์มอย่าง AppMaster แนวคิดเดียวกันนี้จะช่วยให้บล็อกที่ใช้งานซ้ำได้เร็วขึ้น
สถานการณ์ตัวอย่างและขั้นตอนถัดไปสำหรับทีมของคุณ
จินตนาการหน้าผู้ใช้ (Users) ที่คุณกำลังก่อสร้างใหม่: แถบตัวกรอง (สถานะ บทบาท วันที่สร้าง), ตารางที่เลือกแถวได้, การดำเนินการแบบกลุ่ม (ปิดการใช้งาน ลบ ส่งออก), และการควบคุมตามบทบาท (แอดมินเท่านั้นที่ลบแบบกลุ่มได้ ผู้จัดการแก้ไขบทบาทได้)
ด้วย Composition API หรือ Options API UI อาจดูเหมือนกัน แต่โค้ดมักจัดแตกต่าง
ใน Options API คุณมักได้คอมโพเนนต์ใหญ่ตัวเดียวที่มี data สำหรับตัวกรองและการเลือก, computed สำหรับสถานะที่ได้มา, และ methods สำหรับการดึงข้อมูล การดำเนินการแบบกลุ่ม และการเช็กสิทธิ์ การรียูสมักเป็นมิกซ์อินหรือโมดูล helper คุ้นเคย แต่ logic ที่เกี่ยวข้องอาจกระจัดกระจาย (fetching ใน methods, query sync ใน watchers, permissions ใน computed)
ใน Composition API คุณมักแยกหน้าเป็น composable ที่มุ่งเน้น: ตัวหนึ่งสำหรับ query และ filters, ตัวหนึ่งสำหรับการเลือกแถวและการดำเนินการแบบกลุ่ม, และตัวหนึ่งสำหรับ permissions หน้าจะเป็นการประกอบชิ้นส่วนเหล่านี้ และ logic แต่ละส่วนอยู่ด้วยกัน ข้อเสียคือคุณต้องมีชื่อและโฟลเดอร์ชัดเจนเพื่อไม่ให้คนรู้สึกว่า "เวทมนตร์ใน setup"
การรียูสมักปรากฏในไลบรารีแอดมินรอบ ๆ ตัวกรองที่ซิงก์กับ URL, แพทเทิร์นตารางฝั่งเซิร์ฟเวอร์ (pagination, sorting, select-all, guards สำหรับการดำเนินการแบบกลุ่ม), การเช็กสิทธิ์และการปิด UI (ปุ่ม คอลัมน์ การกระทำแถว), และสถานะ empty/loading ที่สอดคล้องระหว่างหน้า
แผนขั้นถัดไปที่ใช้ได้กับทีมส่วนใหญ่:
- เลือกสไตล์เริ่มต้นสำหรับโค้ดใหม่ และอนุญาตข้อยกเว้นเฉพาะเมื่อมีเหตุผลเป็นลายลักษณ์อักษร
- กำหนดคอนเวนชัน: composables อยู่ที่ไหน, ตั้งชื่ออย่างไร, นำเข้าอะไรได้บ้าง, และต้องคืนค่าอะไร
- เพิ่มหน้าตัวอย่างเล็ก ๆ (เช่น Users page นี้) เป็นมาตรฐานสำหรับ pattern และโครงสร้าง
- เขียนเทสต์สำหรับชิ้นส่วนที่รียูสก่อน (filters, permissions, bulk actions) ไม่ใช่เลย์เอาต์
- ถ้าความเร็วสำคัญกว่าการปรับแต่งลึกสำหรับบางหน้า ให้พิจารณาสร้างหน้าพวกนั้นด้วยเครื่องมือ no-code เช่น AppMaster และเก็บไลบรารีที่เขียนมือไว้สำหรับส่วนที่ไม่ธรรมดา
ถ้าคุณกำลังสร้างกับ AppMaster ให้พยายามรักษาโมเดลเดียวกันระหว่างส่วนที่ถูกสร้างและส่วนที่เขียนมือ: สัญญาคอมโพเนนต์คงที่ และ logic ร่วมแพ็กเป็นหน่วยเล็ก ชัดเจน สำหรับทีมที่ประเมิน no-code สำหรับเครื่องมือภายใน AppMaster ถูกออกแบบมาให้สร้างแอปครบวงจร (backend, เว็บ, และมือถือ) ในขณะที่ยังให้คุณมาตรฐาน Vue 3 web UI ในส่วนที่สำคัญ
ถ้าคุณทำสิ่งเดียวในสัปดาห์นี้ ให้ทำหน้า Users เป็นเทมเพลตและบังคับตามมันในการรีวิวโค้ด ตัวอย่างชัดเจนหนึ่งชิ้นจะช่วยความสอดคล้องได้มากกว่าเอกสารยาว
คำถามที่พบบ่อย
เริ่มต้นด้วย Composition API ถ้าไลบรารีของคุณมีพฤติกรรมซ้ำ ๆ เช่น การกรอง, การแบ่งหน้า, การดำเนินการแบบกลุ่ม และการควบคุมสิทธิ์ มันช่วยให้แยก logic เป็น composable ได้ง่ายขึ้นและทำให้การพึ่งพาชัดเจนกว่า ใช้ Options API เมื่อคอมโพเนนต์เป็นเพียงงานนำเสนอและมี logic เบา ๆ
Options API จัดกลุ่มโค้ดตามประเภท (data, methods, computed, watch) ทำให้ logic ของฟีเจอร์หนึ่งๆ มักกระจายอยู่หลายที่ ในขณะที่ Composition API มักจัดกลุ่มตามฟีเจอร์ ดังนั้นทุกอย่างที่เกี่ยวกับ “การกรอง” หรือ “การเลือกแถว” จะอยู่รวมกัน เลือกแนวทางที่ทำให้การเปลี่ยนแปลงครั้งต่อไปหาง่ายและปลอดภัยที่สุด
กับ Options API การนำกลับมาใช้ซ้ำมักเริ่มจาก mixins หรือ extends ซึ่งซ่อนแหล่งที่มาของเมธอดและ computed จนอาจเกิดการชนกันของชื่อได้ Composition API มักใช้ composable ที่มีอินพุตและเอาต์พุตชัดเจน ทำให้การต่อสายงานมองเห็นได้ ในไลบรารีร่วมกัน รูปแบบการใช้งานที่ชัดเจนจะยืดหยุ่นกว่าในระยะยาว
ปฏิบัติต่อ composable แต่ละตัวเหมือน API เล็ก ๆ: ทำหน้าที่เดียว พารามิเตอร์ชัดเจน และค่าที่คืนกลับคาดเดาได้ ถ้ามันเริ่มรวม pagination, caching, permissions และ notifications เข้าด้วยกัน ให้แยกเป็นชิ้นย่อย ยิ่งเล็ก ยิ่งทดสอบง่ายและรียูสง่าย
รักษาสัญญาสาธารณะให้คงที่: props, emitted events, และ slots ควรเปลี่ยนน้อยครั้ง เก็บการฟอร์แมตและ validation พื้นฐานใกล้กับฟิลด์ แต่กฎธุรกิจไว้ใน composable หรือ container เพื่อให้รีแฟกเตอร์ภายในไม่บังคับให้ทุกหน้าต้องเปลี่ยน
Composition API มักทำให้ทดสอบ logic ได้ง่ายขึ้นโดยไม่ต้อง mount คอมโพเนนต์ทั้งตัว เพราะคุณสามารถทดสอบ composable และฟังก์ชันบริสุทธิ์ได้โดยตรง Options API มักผลักให้ต้องทดสอบโดยการ mount ซึ่ง watcher และลำดับ lifecycle อาจเพิ่มความซับซ้อน ไม่ว่าแบบไหน การแยกกฎธุรกิจออกจากการเชื่อมต่อ UI คือสิ่งที่ทำให้เทสต์เล็กและเสถียร
มาตรฐานรูปแบบสถานะแบบ async เดียว เช่น loading, error, และแนวทาง retry/cancel ที่ชัดเจน อย่าให้แต่ละคอมโพเนนต์คิดรูปแบบของตัวเอง เพราะการดีบักจะช้าและไม่สอดคล้อง คุณสามารถใช้ทั้งสอง API ในการนำรูปแบบนี้ไปใช้ได้ แต่ต้องสม่ำเสมอทั่วไลบรารี
Options API อาจทำให้เริ่มต้นได้เร็วในวันแรกเพราะโครงสร้างคุ้นเคย แต่พอ logic กระจายผู้มาใหม่ต้องใช้เวลาเรียงเรื่องราวให้ต่อเนื่อง Composition API จะเร็วกว่าหลังจากคนคุ้นกับ composable และโฟลเดอร์คอนเวนชัน เพราะพฤติกรรมที่เกี่ยวข้องมักถูกจัดรวมกัน ให้ตัวอย่าง “golden” หนึ่งชิ้นและบังคับตาม pattern ในการตรวจสอบโค้ด
เลือกฟีเจอร์ที่มีขอบเขตชัดเจนและใช้งานซ้ำบ่อย เช่น การกรองตารางหรือแมปข้อผิดพลาดของฟอร์ม ทำเป็น pilot: สกัดหน่วยร่วมหนึ่งตัวที่มี API เล็ก ชัดเจน เขียนเทสต์สำหรับหน่วยนั้นก่อน แล้วรีแฟกเตอร์คอมโพเนนต์ตัวเดียวแบบ end-to-end ทดสอบ pattern ในคอมโพเนนต์อีกตัวก่อนจะขยายใช้ในวงกว้าง
สัญญาณเตือนคือ: คอมโพเนนต์ทำงานได้ก็ต่อเมื่อเรียง props/events ตามลำดับเป๊ะ, composable อ่าน/เขียน global state โดยไม่ชัดเจน, watcher หลายตัวแก้ไขสถานะเดียวกัน, รีแฟกเตอร์ทำให้หน้าคอนซูเมอร์พังบ่อย หรือนักพัฒนาหลีกเลี่ยงไฟล์บางไฟล์เพราะดูเสี่ยง ถ้าเห็นสิ่งเหล่านี้ ให้ปรับสัญญาและชัดเจนในการรียูส


