Slotchange และ Assigned Nodes
slotchange event
หัวข้อที่มีชื่อว่า “slotchange event”slotchange event จะ fire บน <slot> element ทุกครั้งที่ชุดของ assigned nodes เปลี่ยนแปลง ซึ่งรวมถึงตอนที่ element เชื่อมต่อกับ DOM เป็นครั้งแรกพร้อมกับมีลูกอยู่แล้ว, ตอนที่มีการเพิ่มหรือลบลูกออกจาก host element และตอนที่ slot attribute ของลูกเปลี่ยนแปลง
ฟัง event นี้ได้โดยตรงบน slot element
const slot = shadow.querySelector('slot');slot.addEventListener('slotchange', () => { console.log('Assigned nodes changed');});assignedNodes และ assignedElements
หัวข้อที่มีชื่อว่า “assignedNodes และ assignedElements”slot.assignedNodes() คืน node ทั้งหมดที่ถูกฉายเข้า slot นั้นในปัจจุบัน รวมถึง text node และ comment node ส่วน slot.assignedElements() คืนเฉพาะ Element node ซึ่งมักจะเป็นสิ่งที่คุณต้องการเมื่อวนลูปดูลูกที่ถูกฉายเข้ามา
ทั้งสองเมธอดรับ options object ที่มี property flatten การส่ง { flatten: true } จะขยาย nested slot element ทั้งหมดแบบเรียกซ้ำเพื่อรวม assigned nodes ของตัวเองด้วย ซึ่งมีประโยชน์เมื่อคุณมี slot ซ้อนอยู่ใน slot
class MyList extends HTMLElement { connectedCallback() { const shadow = this.attachShadow({ mode: 'open' }); shadow.innerHTML = '<slot></slot>'; const slot = shadow.querySelector('slot'); slot.addEventListener('slotchange', () => { const nodes = slot.assignedElements(); console.log('Slot has', nodes.length, 'elements'); }); }}customElements.define('my-list', MyList);การใช้งานในทางปฏิบัติ
หัวข้อที่มีชื่อว่า “การใช้งานในทางปฏิบัติ”การตอบสนองต่อ slotchange ช่วยให้คอมโพเนนต์ของคุณซิงค์กับลูกที่ถูกฉายเข้ามาได้โดยไม่ต้อง poll หรือใช้ mutation observer แพทเทิร์นที่พบบ่อยได้แก่:
- การนับจำนวน item ที่ถูกฉายเข้ามาและสะท้อนจำนวนนั้นใน shadow DOM หรือเป็น attribute
- การตรวจสอบว่ามีเฉพาะ element ชนิดที่คาดหวังเท่านั้นที่ถูก slot เข้ามา
- การซิงค์
aria-labelหรือ accessibility attribute อื่น ๆ เมื่อจำนวน item เปลี่ยนแปลง - การผูก event listener เข้ากับลูกที่เพิ่งถูกฉายเข้ามา
ประเด็นสำคัญคือ คอมโพเนนต์ของคุณไม่ได้เป็นเจ้าของลูกใน light DOM — ผู้ใช้งานต่างหากที่เป็นเจ้าของ slotchange event คือสะพานที่ช่วยให้คุณตอบสนองต่อการเปลี่ยนแปลงจากภายนอกเหล่านั้น
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
ฟัง slotchange event เพื่อซิงค์สถานะ | คอมโพเนนต์ตอบสนองต่อการเปลี่ยนแปลงเนื้อหาแบบ real-time โดยไม่ต้อง poll | ต้องเขียน event listener และจัดการ cleanup เพิ่มเติม เพิ่มความซับซ้อนของ lifecycle |
ใช้ MutationObserver แทน slotchange | ตรวจจับการเปลี่ยนแปลงได้ละเอียดกว่า เช่น การเปลี่ยน attribute หรือ text content ภายในลูก | มี overhead สูงกว่าและตั้งค่าซับซ้อนกว่า slotchange ซึ่งถูกออกแบบมาสำหรับ use case นี้โดยเฉพาะ |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- ลืมฟัง
slotchangeevent เมื่อเนื้อหาที่ถูกฉายเข้า slot เปลี่ยนแปลงแบบไดนามิก — ทำให้ UI ภายใน shadow DOM ค้างอยู่กับข้อมูลเก่า ไม่อัปเดตตามลูกที่ถูกเพิ่มหรือลบออกจริง - ใช้
slot.assignedNodes()แล้วคาดหวังว่าจะได้เฉพาะ Element —assignedNodes()คืน text node และ comment node ด้วย ถ้าต้องการเฉพาะ element ควรใช้slot.assignedElements()แทน - ลืมส่ง
{ flatten: true }เมื่อมี slot ซ้อนอยู่ใน slot — ทำให้ได้ assigned nodes ไม่ครบ เพราะ node ที่อยู่ใน nested slot จะไม่ถูกรวมเข้ามาโดย default
💡 ตัวอย่างจากของจริง
Lit ใช้แพทเทิร์นการฟัง
slotchangeร่วมกับassignedElements()ในคอมโพเนนต์ที่ต้องนับหรือตรวจสอบชนิดของลูกที่ถูกฉายเข้ามา เช่น tab list ที่ต้องรู้ว่ามี tab กี่ตัวShoelace ใช้
slotchangeภายในคอมโพเนนต์อย่าง<sl-select>เพื่อตรวจจับเมื่อ option ที่ถูกฉายเข้ามาเปลี่ยนแปลง แล้วซิงค์สถานะ selected ให้ตรงกัน