先记住这个答案
普通 Event 适合表达“发生了某事”;组件还想告诉外部“选中了哪条记录”,可用 CustomEvent 的 detail 传 { id, label } 等数据。通过 dispatchEvent() 派发时,监听器同步运行。自定义事件默认不冒泡、不跨 Shadow DOM 边界;需要外层容器接收时,要显式设置 bubbles: true,跨影子边界还需 composed: true。
detail携带自定义数据dispatchEvent()同步调用监听器bubbles默认是false- 跨 Shadow DOM 还要考虑
composed
列表项通知外层选中了谁
组件内部不必直接调用外部页面的函数。选中一行时派发语义明确的事件,让使用组件的页面订阅,再决定跳转还是更新详情。
const list = document.querySelector('#result-list')
list.addEventListener('item-select', event => {
console.log(event.detail.id) // 42
})
list.dispatchEvent(new CustomEvent('item-select', {
detail: { id: '42', label: '订单 42' },
bubbles: true
}))示例先在同一目标注册监听器,再派发事件,因此会读到 42。真实组件通常在用户选择时派发;detail 是业务自定义负载,浏览器不会验证其中有哪些字段。
若跨模块约定事件名和负载结构,应在组件接口里写清楚。对象里的值是引用,不是不可变快照;监听器修改它可能影响其他监听器。
传播选项要按组件边界选择
若事件只给当前目标上的监听器使用,默认不冒泡即可。若外层容器要通过事件委托接收,设 bubbles: true。Web Component 内部事件要穿过 Shadow DOM,通常还要 composed: true。
传播范围越大,越要避免事件名与页面其他组件冲突。对于不希望外部直接看到的内部状态,不要随意开放跨影子边界的事件。
detail 的形状应当当作组件公开接口维护。例如外部已经按 event.detail.id 读取,再把它改成 itemId 就会让订阅方静默失效;改动时应同步更新使用者或提供过渡。事件名也一样,最好表达已经发生的业务事实,例如“某项已选中”,而不是把内部按钮点击细节泄漏出去。
设置 cancelable: true 后,监听方可通过 preventDefault() 否决某项默认动作,派发方则检查 dispatchEvent() 的返回值。只在组件确实要给外部“阻止此动作”的权力时才这样设计;普通通知事件不必做成可取消。
容易答错的地方
- 以为
detail会自动序列化或复制 - 传对象时仍是引用,多个监听器可能看到同一对象。
- 以为自定义事件默认向上冒泡
bubbles默认是false,外层委托监听可能收不到。
面试官还会怎么问?
CustomEvent 可以取消吗?
构造时设置 cancelable: true,监听器才能用 preventDefault() 表达取消。
它会异步运行监听器吗?
通过 dispatchEvent() 派发时,监听器同步执行。
Shadow DOM 外收不到怎么查?
检查事件是否冒泡,以及是否设置 composed: true 允许跨边界。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。