Custom Events
Custom elements สื่อสารออกไปภายนอกด้วยการ dispatch events คอนสตรัคเตอร์ CustomEvent ที่มีมาใน browser ช่วยให้คุณแนบ payload ตามต้องการ — ซึ่งก็คือออบเจกต์ detail — เพื่อให้ parent รับข้อมูลที่มีโครงสร้างได้โดยไม่ต้องยื่นมือเข้าไปภายใน element นี้ หน้านี้จะอธิบายวิธีสร้าง ส่ง และรับฟัง custom events
คอนสตรัคเตอร์ CustomEvent
หัวข้อที่มีชื่อว่า “คอนสตรัคเตอร์ CustomEvent”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 ออกมาเท่านั้น และจะไม่ส่งต่อขึ้นไปอีก
การ Dispatch จากภายใน Element
หัวข้อที่มีชื่อว่า “การ Dispatch จากภายใน Element”เรียก 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 listeningdocument.querySelector('my-counter').addEventListener('count-change', (e) => { console.log('Count is now', e.detail.count);});คุณยังสามารถรับฟังที่ระดับ document ได้เมื่อตั้ง bubbles: true ไว้ ซึ่งสะดวกมากเมื่อคุณไม่มีการอ้างอิงโดยตรงไปยัง element
ทำไมต้องใช้ Events แทน Callback Props?
หัวข้อที่มีชื่อว่า “ทำไมต้องใช้ Events แทน Callback Props?”รูปแบบ 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
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
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จาก listener —detailควรถือเป็น 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-selectdispatch eventsl-changeพร้อมdetailที่บอก item ที่ถูกเลือก ทำให้ consumer อ่านค่าที่เปลี่ยนได้โดยไม่ต้อง query เข้าไปใน shadow DOM เอง