事件
事件是元素之间通信更改的标准方式。这些更改通常是由于用户交互引起的。例如,按钮在用户点击时会分发一个 click 事件;当用户在输入框中输入值时,输入框会分发一个 change 事件。
除了这些自动分发的标准事件之外,Lit 元素还可以分发自定义事件。例如,菜单元素可以分发一个事件来指示所选项目已更改;弹出窗口元素可以在弹出窗口打开或关闭时分发事件。
任何 Javascript 代码,包括 Lit 元素本身,都可以监听事件并根据事件采取行动。例如,工具栏元素可能会在菜单项被选中时过滤列表;登录元素可能会在处理登录按钮的点击事件时处理登录。
监听事件
“监听事件”的永久链接除了标准的 addEventListener
API 之外,Lit 还引入了一种声明性的方式来添加事件监听器。
在元素模板中添加事件监听器
“在元素模板中添加事件监听器”的永久链接你可以在模板中使用 @
表达式来在组件模板中的元素上添加事件监听器。声明式事件监听器在渲染模板时添加。
自定义事件监听器选项
“自定义事件监听器选项”的永久链接如果你需要自定义用于声明式事件监听器的事件选项(例如 passive
或 capture
),你可以在使用 @eventOptions
装饰器在监听器上指定这些选项。传递给 @eventOptions
的对象将作为 options
参数传递给 addEventListener
。
import {LitElement, html} from 'lit';
import {eventOptions} from 'lit/decorators.js';
//...
@eventOptions({passive: true})
private _handleTouchStart(e) { console.log(e.type) }
**使用装饰器。**装饰器是一个提议的 JavaScript 特性,因此你需要使用像 Babel 或 TypeScript 这样的编译器来使用装饰器。有关详细信息,请参阅 启用装饰器。
如果你没有使用装饰器,你可以通过将对象传递给事件监听器表达式来自定义事件监听器选项。该对象必须具有一个 handleEvent()
方法,并且可以包含任何通常出现在 addEventListener()
的 options
参数中的选项。
render() {
return html`<button @click=${{handleEvent: () => this.onClick(), once: true}}>click</button>`
}
在组件或其 shadow root 中添加事件监听器
“在组件或其 shadow root 中添加事件监听器”的永久链接要接收从组件的插槽子元素以及通过组件模板渲染到 shadow DOM 的子元素分发的事件通知,你可以使用标准的 addEventListener
DOM 方法在组件本身添加监听器。有关完整详细信息,请参阅 MDN 上的 EventTarget.addEventListener()。
组件构造函数是在组件上添加事件监听器的合适位置。
constructor() {
super();
this.addEventListener('click', (e) => console.log(e.type, e.target.localName));
}
在组件本身添加事件监听器是事件委托的一种形式,可以减少代码或提高性能。有关详细信息,请参阅 事件委托。通常在这样做时,事件的 target
属性用于根据哪个元素触发事件来采取行动。
但是,从组件的 shadow DOM 中触发的事件在被组件上的事件监听器听到时会被重新定位。这意味着事件目标是组件本身。有关更多信息,请参阅 在 Shadow DOM 中使用事件。
重新定位会干扰事件委托,为了避免这种情况,可以将事件监听器添加到组件的 shadow root 本身。由于 shadowRoot
在 constructor
中不可用,因此事件监听器可以像下面这样在 createRenderRoot
方法中添加。请注意,确保从 createRenderRoot
方法返回 shadow root 很重要。
在其他元素上添加事件监听器
“在其他元素上添加事件监听器”的永久链接如果你的组件向除自身或其模板化 DOM 之外的任何内容添加事件监听器 - 例如,向 Window
、Document
或主 DOM 中的某个元素添加监听器 - 你应该在 connectedCallback
中添加监听器,并在 disconnectedCallback
中移除它。
在
disconnectedCallback
中移除事件监听器可确保组件销毁或从页面断开连接时,组件分配的任何内存都会被清理。在
connectedCallback
中添加事件监听器(而不是例如构造函数或firstUpdated
)可确保组件在断开连接后重新连接到 DOM 时重新创建其事件监听器。
connectedCallback() {
super.connectedCallback();
window.addEventListener('resize', this._handleResize);
}
disconnectedCallback() {
window.removeEventListener('resize', this._handleResize);
super.disconnectedCallback();
}
有关 connectedCallback
和 disconnectedCallback
的更多信息,请参阅 MDN 关于使用自定义元素的文档 生命周期回调。
优化性能
“优化性能”的永久链接添加事件监听器速度非常快,通常不是性能问题。但是,对于高频使用且需要大量事件监听器的组件,你可以通过使用 事件委托 减少使用的监听器数量,并在渲染后 异步 添加监听器来优化第一次渲染性能。
事件委托
“事件委托”的永久链接使用事件委托可以减少使用的事件监听器数量,从而提高性能。它有时也很方便,可以将事件处理集中化以减少代码。事件委托只能用于处理 冒泡
的事件。有关冒泡的详细信息,请参阅 分发事件。
冒泡事件可以在 DOM 中的任何祖先元素上被听到。你可以利用这一点,在祖先组件上添加一个事件监听器,以接收其 DOM 中任何后代元素分发的冒泡事件通知。使用事件的 target
属性根据分发事件的元素采取具体行动。
异步添加事件监听器
“异步添加事件监听器”的永久链接要在渲染后添加事件监听器,请使用 firstUpdated
方法。这是一个 Lit 生命周期回调,在组件第一次更新并渲染其模板化 DOM 后运行。
firstUpdated
回调在你的组件第一次更新并调用其 render
方法后触发,但在浏览器有机会绘制之前触发。
有关更多信息,请参阅生命周期文档中的 firstUpdated。
为了确保监听器在用户可以看到组件后添加,你可以等待一个在浏览器绘制后解析的 Promise。
async firstUpdated() {
// Give the browser a chance to paint
await new Promise((r) => setTimeout(r, 0));
this.addEventListener('click', this._handleClick);
}
理解事件监听器中的 this
“理解事件监听器中的 this”的永久链接 使用模板中的声明式 @
语法添加的事件监听器会自动绑定到组件。
因此,你可以在任何声明式事件处理程序中使用 this
来引用你的组件实例
class MyElement extends LitElement {
render() {
return html`<button @click="${this._handleClick}">click</button>`;
}
_handleClick(e) {
console.log(this.prop);
}
}
使用 addEventListener
声明性地添加监听器时,你需要使用箭头函数,以便 this
指向组件
export class MyElement extends LitElement {
private _handleResize = () => {
// `this` refers to the component
console.log(this.isConnected);
}
constructor() {
window.addEventListener('resize', this._handleResize);
}
}
有关更多信息,请参阅 MDN 上的 this
的文档。
监听从重复模板中触发的事件
“监听从重复模板中触发的事件”的永久链接监听重复项目上的事件时,如果事件冒泡,使用 事件委托 通常很方便。当事件不冒泡时,可以在重复元素上添加监听器。以下是两种方法的示例
移除事件监听器
“移除事件监听器”的永久链接将 null
、undefined
或 nothing
传递给 @
表达式将导致任何现有监听器被移除。
分发事件
“分发事件”的永久链接所有 DOM 节点可以使用 dispatchEvent
方法分发事件。首先,创建一个事件实例,指定事件类型和选项。然后将它传递给 dispatchEvent
,如下所示
const event = new Event('my-event', {bubbles: true, composed: true});
myElement.dispatchEvent(event);
bubbles
选项允许事件向上流经 DOM 树到分发元素的祖先。如果你希望事件能够参与 事件委托,设置此标志很重要。
composed
选项用于允许事件在元素所在的 Shadow DOM 树之上分发。
有关更多信息,请参见 在 Shadow DOM 中使用事件。
有关分发事件的完整描述,请参阅 MDN 上的 EventTarget.dispatchEvent()。
何时分发事件
指向“何时分发事件”的永久链接事件应在响应用户交互或组件状态的异步更改时分发。它们通常**不应**在响应组件所有者通过其属性或属性 API 所做的状态更改时分发。这通常是原生 Web 平台元素的工作方式。
例如,当用户在 input
元素中键入值时,会分发 change
事件,但如果代码设置 input
的 value
属性,则**不会**分发 change
事件。
同样,菜单组件应在用户选择菜单项时分发事件,但如果例如菜单的 selectedItem
属性被设置,则不应分发事件。
这通常意味着组件应响应它正在监听的另一个事件分发事件。
在元素更新后分发事件
指向“元素更新后分发事件”的永久链接通常,只有在元素更新并呈现后才应触发事件。如果事件旨在基于用户交互传达呈现状态的更改,则可能需要这样做。在这种情况下,可以在更改状态后但分发事件之前等待组件的 updateComplete
Promise。
使用标准或自定义事件
指向“使用标准事件或自定义事件”的永久链接事件可以通过构建 Event
或 CustomEvent
来分发。两种方法都是合理的。使用 CustomEvent
时,所有事件数据都将传递到事件的 detail
属性中。使用 Event
时,可以创建事件子类并附加自定义 API。
有关构建事件的详细信息,请参阅 MDN 上的 Event。
触发自定义事件
指向“触发自定义事件”的永久链接const event = new CustomEvent('my-event', {
detail: {
message: 'Something important happened'
}
});
this.dispatchEvent(event);
有关更多信息,请参见 MDN 上有关自定义事件的文档。
触发标准事件
指向“触发标准事件”的永久链接class MyEvent extends Event {
constructor(message) {
super();
this.type = 'my-event';
this.message = message;
}
}
const event = new MyEvent('Something important happened');
this.dispatchEvent(event);
在 Shadow DOM 中使用事件
指向“在 Shadow DOM 中使用事件”的永久链接使用 Shadow DOM 时,标准事件系统有一些重要的修改需要理解。Shadow DOM 主要用于在 DOM 中提供作用域机制,该机制封装了有关这些“影子”元素的详细信息。因此,Shadow DOM 中的事件将某些详细信息封装在外部 DOM 元素之外。
理解事件的组合分发
指向“了解组合事件分发”的永久链接默认情况下,在 Shadow DOM 根元素内部分发的事件在 Shadow DOM 根元素外部不可见。为了使事件穿过 Shadow DOM 边界,必须将 composed
属性 设置为 true
。通常将 composed
与 bubbles
配合使用,以便 DOM 树中的所有节点都可以看到事件。
_dispatchMyEvent() {
let myEvent = new CustomEvent('my-event', {
detail: { message: 'my-event happened.' },
bubbles: true,
composed: true });
this.dispatchEvent(myEvent);
}
如果事件是 composed
并且是 bubble
,则可以由分发事件的元素的所有祖先接收,包括外部 Shadow DOM 根元素中的祖先。如果事件是 composed
但不是 bubble
,则只能由分发事件的元素和包含 Shadow DOM 根元素的主元素接收。
请注意,大多数标准用户界面事件,包括所有鼠标、触摸和键盘事件,都是同时冒泡和组合的。有关更多信息,请参见 MDN 上有关组合事件的文档。
理解事件重新定位
指向“了解事件重新定位”的永久链接从 Shadow DOM 根元素内部分发的 组合 事件会被重新定位,这意味着对于托管 Shadow DOM 根元素的元素或其任何祖先上的任何侦听器,这些事件看起来像是来自托管元素。由于 Lit 组件渲染到 Shadow DOM 根元素中,因此从 Lit 组件内部分发的所有组合事件看起来像是由 Lit 组件本身分发的。事件的 target
属性是 Lit 组件。
<my-element onClick="(e) => console.log(e.target)"></my-element>
render() {
return html`
<button id="mybutton" @click="${(e) => console.log(e.target)}">
click me
</button>`;
}
在需要确定事件来源的高级情况下,请使用 event.composedPath()
API。此方法返回事件分发遍历的所有节点的数组,包括 Shadow DOM 根元素中的节点。因为这会破坏封装,所以应注意避免依赖可能暴露的实现细节。常见用例包括确定点击的元素是否为锚点标签,用于客户端路由目的。
handleMyEvent(event) {
console.log('Origin: ', event.composedPath()[0]);
}
有关更多信息,请参见 MDN 上有关 composedPath 的文档。
事件分发器和监听器之间的通信
指向“在事件分发器和侦听器之间进行通信”的永久链接事件主要用于将更改从事件分发器传达给事件侦听器,但事件也可以用于将信息从侦听器传达回分发器。
您可以通过一种方法来实现,那就是在事件上公开侦听器可以用来定制组件行为的 API。例如,侦听器可以设置自定义事件的 detail
属性上的一个属性,然后分发组件将其用于定制行为。
在分发器和侦听器之间进行通信的另一种方法是通过 preventDefault()
方法。可以调用它来指示事件的标准操作不应发生。当侦听器调用 preventDefault()
时,事件的 defaultPrevented
属性将变为 true
。然后,侦听器可以使用此标志来定制行为。
以下示例使用这两种技术