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

Custom Events

Custom elements สื่อสารออกไปภายนอกด้วยการ dispatch events คอนสตรัคเตอร์ CustomEvent ที่มีมาใน browser ช่วยให้คุณแนบ payload ตามต้องการ — ซึ่งก็คือออบเจกต์ detail — เพื่อให้ parent รับข้อมูลที่มีโครงสร้างได้โดยไม่ต้องยื่นมือเข้าไปภายใน element นี้ หน้านี้จะอธิบายวิธีสร้าง ส่ง และรับฟัง custom events

CustomEvent ถูกสร้างด้วย string ที่บอก type และออบเจกต์ options ตัวเลือกที่สำคัญที่สุดคือ detail ที่เป็นที่เก็บ payload ของคุณ:

var event = new CustomEvent('count-change', {
detail: { count: 42 },
bubbles: true,
});

bubbles: true ทำให้ event เดินทางขึ้นผ่าน DOM tree ดังนั้น parent หรือ ancestor — ไม่ใช่แค่ element ตัวเอง — ก็สามารถรับฟัง event นี้ได้ หากไม่มี flag นี้ event จะยิงขึ้นเฉพาะบน element ที่ dispatch ออกมาเท่านั้น และจะไม่ส่งต่อขึ้นไปอีก

เรียก this.dispatchEvent(event) จากที่ใดก็ได้ภายในคลาสของคุณเพื่อยิง event ออกไป:

class MyCounter extends HTMLElement {
connectedCallback() {
this._count = 0;
this.innerHTML = '<button>Count: 0</button>';
this.querySelector('button').addEventListener('click', () => {
this._count++;
this.querySelector('button').textContent = 'Count: ' + this._count;
this.dispatchEvent(new CustomEvent('count-change', {
detail: { count: this._count },
bubbles: true,
}));
});
}
}
customElements.define('my-counter', MyCounter);

Element ไม่สนใจว่าใครกำลังฟังอยู่ แค่ประกาศว่ามีบางอย่างเกิดขึ้นแล้วส่งข้อมูลออกไป นี่คือกุญแจสำคัญในการทำให้ elements นำกลับมาใช้ใหม่ได้

โค้ดใดก็ตามที่มีการอ้างอิงถึง element — หรือ ancestor ใดก็ได้ เพราะตั้ง bubbles: true ไว้ — สามารถลงทะเบียน listener ด้วย API มาตรฐาน addEventListener ได้:

// Parent listening
document.querySelector('my-counter').addEventListener('count-change', (e) => {
console.log('Count is now', e.detail.count);
});

คุณยังสามารถรับฟังที่ระดับ document ได้เมื่อตั้ง bubbles: true ไว้ ซึ่งสะดวกมากเมื่อคุณไม่มีการอ้างอิงโดยตรงไปยัง element

รูปแบบ callback-prop — การส่งฟังก์ชันเป็น property — ใช้งานได้ใน JavaScript แต่สร้างการผูกมัดที่แน่นแฟ้น: element ต้องรู้ว่า consumer คาดหวังฟังก์ชันบน property ที่ชื่อเฉพาะ และ consumer ต้องมั่นใจว่าตั้งค่าฟังก์ชันนั้นไว้ก่อนที่ element จะใช้

Events ถูกออกแบบมาให้มีการเชื่อมต่อแบบหลวม ผู้ฟังจำนวนมากเท่าไรก็ได้สามารถ subscribe หรือ unsubscribe ได้ทุกเวลาโดยที่ element ไม่รับรู้ public API ของ element ยังคงคาดเดาได้: จะ dispatch named event พร้อม detail shape ที่มีเอกสารกำกับ และไม่มีอะไรมากกว่านั้น

Element click-counter ด้านล่างนับจำนวนครั้งที่ปุ่มของตัวเองถูกกด แต่ละครั้งที่กดจะ dispatch event counter-change พร้อม { value: N } ใน detail หน้าเพจรับฟังที่ระดับ document และแสดงค่าล่าสุดในพื้นที่ log

ตัวเลือกBenefitCost
CustomEvent + detail payloadข้อมูลที่ส่งออกมีโครงสร้างชัดเจน เชื่อมต่อแบบหลวม หลาย listener subscribe พร้อมกันได้โดย element ไม่ต้องรู้ว่าใครฟังอยู่ไม่มี type-checking ในตัว ต้องพึ่งเอกสารกำกับ shape ของ detail เอง ผู้ใช้ component อาจเข้าใจผิดได้ถ้าไม่มีเอกสาร
Callback prop (ส่งฟังก์ชันเป็น property)เรียกกลับตรงไปตรงมา รับ return value จาก caller กลับมาใช้ต่อได้ผูกมัดแน่นกับ consumer เฉพาะราย รองรับผู้ฟังได้แค่คนเดียวต่อครั้ง และต้องตั้งค่า callback ก่อน element ใช้งาน
  • ลืม bubbles: true — ยิง CustomEvent โดยไม่ตั้ง bubbles ทำให้เฉพาะ element ที่ dispatch เท่านั้นที่รับรู้ event ancestor หรือ document จะไม่มีทางเห็น event เลย
  • แก้ไขค่าใน e.detail จาก listenerdetail ควรถือเป็น snapshot ที่ไม่เปลี่ยนแปลงของ state ณ ขณะ dispatch การแก้ไขค่าจาก listener หนึ่งจะกระทบ listener อื่นที่รับ event เดียวกันแบบไม่คาดคิด
  • ตั้งชื่อ event แบบ camelCase เช่น countChange — ควรใช้รูปแบบตัวพิมพ์เล็กคั่นด้วยขีดกลางแบบ DOM event มาตรฐาน เช่น count-change เพื่อหลีกเลี่ยงปัญหา case-sensitivity ใน HTML attribute event handler

💡 ตัวอย่างจากของจริง

GitHub’s Catalyst framework — ใช้แบบแผน CustomEvent พร้อม detail อย่างเคร่งครัดสำหรับ custom element ทุกตัว เพื่อให้ event payload คาดเดาได้และทดสอบง่าย

Shoelace / Web Awesome design system — component เช่น sl-select dispatch event sl-change พร้อม detail ที่บอก item ที่ถูกเลือก ทำให้ consumer อ่านค่าที่เปลี่ยนได้โดยไม่ต้อง query เข้าไปใน shadow DOM เอง

Property ใดของ `CustomEvent` ที่ใช้เก็บ event payload?
ตัวเลือกใดทำให้ CustomEvent เดินทางขึ้นไปใน DOM tree?
จะ dispatch custom event จากภายใน element ได้อย่างไร?