ข้ามไปยังเนื้อหา

Slotchange และ Assigned Nodes

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');
});

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 คือสะพานที่ช่วยให้คุณตอบสนองต่อการเปลี่ยนแปลงจากภายนอกเหล่านั้น

ตัวเลือกBenefitCost
ฟัง slotchange event เพื่อซิงค์สถานะคอมโพเนนต์ตอบสนองต่อการเปลี่ยนแปลงเนื้อหาแบบ real-time โดยไม่ต้อง pollต้องเขียน event listener และจัดการ cleanup เพิ่มเติม เพิ่มความซับซ้อนของ lifecycle
ใช้ MutationObserver แทน slotchangeตรวจจับการเปลี่ยนแปลงได้ละเอียดกว่า เช่น การเปลี่ยน attribute หรือ text content ภายในลูกมี overhead สูงกว่าและตั้งค่าซับซ้อนกว่า slotchange ซึ่งถูกออกแบบมาสำหรับ use case นี้โดยเฉพาะ
  • ลืมฟัง slotchange event เมื่อเนื้อหาที่ถูกฉายเข้า slot เปลี่ยนแปลงแบบไดนามิก — ทำให้ UI ภายใน shadow DOM ค้างอยู่กับข้อมูลเก่า ไม่อัปเดตตามลูกที่ถูกเพิ่มหรือลบออกจริง
  • ใช้ slot.assignedNodes() แล้วคาดหวังว่าจะได้เฉพาะ ElementassignedNodes() คืน 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 ให้ตรงกัน

slotchange event fire บน slot element เมื่อใด?
slot.assignedElements() คืนค่าอะไรเมื่อเทียบกับ slot.assignedNodes()?
เมื่อส่ง { flatten: true } เข้าไปใน assignedNodes() จะทำอะไร?