使用事件通信
LWC 派发标准 DOM 事件,也可创建和派发自定义事件。事件用于向上(沿组件包含层次)通信——子组件通知父组件用户操作。向下通信通过属性传递或方法调用。跨组件通信使用 Lightning Message Service。
强烈推荐使用 CustomEvent 接口——跨浏览器一致性更好、无需设置或模板代码、可通过 detail 属性传递任意数据。事件命名规则:不要用大写字母和空格、用下划线分隔单词、不要加 on 前缀(会导致 ononmessage 这样的混淆)。
创建和派发事件
用 CustomEvent() 构造函数创建事件,用 this.dispatchEvent() 派发。完整示例——分页器组件:
<!-- paginator.html -->
<lightning-button label="Previous" onclick={previousHandler}></lightning-button>
<lightning-button label="Next" onclick={nextHandler}></lightning-button>
// paginator.js
previousHandler() { this.dispatchEvent(new CustomEvent("previous")); }
nextHandler() { this.dispatchEvent(new CustomEvent("next")); }
父组件监听——HTML 属性语法 on{eventtype}:
<c-paginator onprevious={previousHandler} onnext={nextHandler}></c-paginator>
page = 1;
previousHandler() { if (this.page > 1) this.page--; }
nextHandler() { this.page++; }
这些是简单的"某事发生了"事件——不传递数据载荷,仅声明按钮被点击。参考 lwc-recipes eventSimple + paginator。
在事件中传递数据 —— detail 属性
用 CustomEvent 的 detail 属性向上传递数据。关键原则:仅发送原始类型——JavaScript 对非原始类型使用引用传递,任何监听器可以在组件不知情的情况下修改对象。如需要传对象,先拷贝到新对象再放入 detail。
// contactListItem.js —— 子组件派发选中事件
selectHandler(event) {
event.preventDefault();
const selectedEvent = new CustomEvent("selected", { detail: this.contact.Id });
this.dispatchEvent(selectedEvent);
}
// eventWithData.js —— 父组件接收 ID 并查找对应联系人
contactSelected(event) {
const contactId = event.detail;
this.selectedContact = this.contacts.data.find(contact => contact.Id === contactId);
}
<c-contact-list-item contact={contact} onselected={contactSelected}>
最佳实践:不要将 @api 或 @wire 的非原始值直接放入 detail——这些值被只读膜包装,在跨 LWC-Aura 桥接时只读膜可能丢失导致数据被意外修改。参考 lwc-recipes eventWithData。
处理事件 —— 声明式与 lwc:on
声明式监听(推荐——减少代码量):模板中用 on{eventname} 属性。
lwc:on 多重监听:一个对象绑定多个事件处理器——属性键为事件类型字符串,值为处理器函数。处理器自动绑定到组件实例,this 可访问组件属性和方法:
eventHandlers = { click: this.handleClick, mouseover: this.handleMouseOver };
<lightning-button lwc:on={eventHandlers} label="Click me"></lightning-button>
lwc:on 动态切换:可通过重新赋值 eventHandlers 对象动态切换监听的事件类型——父组件在运行时决定监听 customEvent 还是 anotherCustomEvent。
处理事件 —— 命令式与事件重定向
命令式监听(不推荐):在 JS 中用 addEventListener。两套语法:Shadow 边界内的元素用 this.template.addEventListener();非模板拥有的元素(如通过插槽传入的)用 this.addEventListener()。框架管理组件生命周期内监听器的清理——但如果添加到 window、document 等外部对象,你必须自己在 disconnectedCallback 中移除。
反模式:不要用addEventListener("event", this.handler.bind(this))——bind()每次返回新函数,导致removeEventListener无法匹配,造成内存泄漏。用箭头函数类字段代替。
事件重定向(Event Retargeting)
事件冒泡跨越 Shadow 边界时,Event.target 的值改变以匹配监听器的作用域——保护 Shadow DOM 封装。例如 <my-button> 内的 <button> 被点击,外部监听器看到 Event.target === my-button,而非内部 button。在 todo-item 内部,target 是 div;在 todo-app 监听器中,target 是 c-todo-item。
配置事件传播 —— bubbles 与 composed
事件通过冒泡阶段传播(不支持捕获阶段)。两个布尔属性控制传播行为——默认均为 false:
- bubbles:是否通过 DOM 向上冒泡。默认 false
- composed:是否能穿越 Shadow 边界。默认 false
三种实用配置组合(第四种 bubbles:false, composed:true LWC 不使用)。辅助属性:Event.target——派发事件的元素(受重定向影响);Event.currentTarget——当前处理器的附着元素(始终不变);Event.composedPath()——事件遍历路径的数组。
配置 1:bubbles:false, composed:false(推荐——默认)
事件不冒泡、不穿越 Shadow 边界。只能通过直接在派发组件上附加监听器来捕获。事件仅到达 c-child 自身。处理器中 event.currentTarget = c-child,event.target = c-child。
推荐此配置——破坏性最小、提供最佳封装。它不会强制消费组件和祖先组件将事件类型纳入其公共 API。参考 lwc-recipes 中 eventWithData 的 contactListItem 就是使用此配置。
配置 2:bubbles:true, composed:false
事件在Shadow 边界内冒泡,但不穿越边界。c-child 和 div.wrapper 都能响应。处理器中 c-child 的 target 是 c-child;div.wrapper 的 currentTarget 是 div.wrapper,target 是 c-child。
两种用途:① 创建内部事件:在模板中的元素上派发——仅冒泡到该模板内的祖先元素,到达 Shadow 边界即停止。外部父组件的处理器不执行。② 向祖父组件发送事件:如果组件通过插槽传入,在宿主元素上派发——事件仅在包含该组件的模板中可见。参考 lwc-recipes eventBubbling 组件。
配置 3:bubbles:true, composed:true
事件冒泡穿越所有 Shadow 边界,一直传播到文档根节点。重要警告:使用此配置 → 事件类型成为组件公共 API 的一部分,同时强制消费组件和所有祖先将事件纳入其 API。可能造成名称冲突——导致错误的监听器被触发。
如果必须使用:给事件类型加命名空间前缀(如 mydomain__myevent),但 HTML 监听器名会变成 onmydomain__myevent——很别扭。尽量避免。
配置 4 与跨 DOM 通信
bubbles:false, composed:true——LWC 不使用此配置。
跨 DOM 通信
不在同一 DOM 树中的组件间通信——首选 Lightning Message Service(lightning/messageService):① 声明消息通道(LightningMessageChannel 元数据);② 用 publish() 发布消息;③ 用 subscribe()/unsubscribe() 订阅。优势:不限于单页面、跨 LWC/Aura/Visualforce、跨标签页/弹出窗口、跨命名空间。
pubsub 模块(不推荐):仅在不支持 LMS 的容器中使用。限制于单页面,需手动注销所有注册组件。不是官方支持或主动维护的模块。
事件最佳实践
detail 数据原则:
- 同 Shadow 树通信 → 不要加
myProperty到 detail——消费者可用event.target.myProperty - 跨 Shadow 树通信 → 必须用 detail(event.target.* 不工作——真实目标不可见)
- 始终用原始类型——非原始值会被任何监听器修改
- 如必须传非原始值 → 拷贝到新对象再放入 detail
- 不要将 @api 或 @wire 的非原始值直接放入 detail——只读膜可能在 IE11/跨桥接时丢失
传播配置原则:优先 bubbles:false, composed:false——破坏性最小。不要用 bubbling + composed——整个 DOM 树都会收到。如果用了 composed:true,事件类型应全局唯一。
动态事件监听器 —— lwc:on 注意事项
使用 lwc:on 的关键规则:
- 不能修改传入 lwc:on 的对象引用的属性——必须传新对象来修改
- 可以重新赋值变量到新对象——省略属性→移除对应监听器;新增属性→添加监听器;属性都存在→更新监听器
- 推荐用 lwc:on 而非 lwc:spread 动态添加事件监听器
- 同一事件类型同时使用 lwc:on 和 onevent 监听器会抛出错误
- 同时用 lwc:on 和 lwc:spread 指定同一事件类型 → 两个监听器都会被添加
完整动态组件事件方案:lwc:component + lwc:is(加载) → lwc:spread(属性) → lwc:on(事件监听器)。
感谢阅读本指南。如需继续学习,请参阅下一章:使用 Salesforce 数据。