事件
在元素模板中添加事件监听器
可以使用@在模板中使用表达式将事件侦听器添加到组件模板中的元素。渲染模板时添加声明性事件侦听器。
如果熟悉vue的话,那对@应该十分属性
@customElement("base-app")
export class BaseApp extends LitElement {
@property({ type: Number })
count: number = 0;
// 渲染组件
protected render() {
return html`
<div>
<button @click=${this.addNumber}>加一</button>
<div>当前数值是:${this.count}</div>
</div>
`;
}
addNumber() {
this.count += 1;
}
}
向组件或其影子根添加事件监听器
可以使用标准addEventListenerDOM 方法向组件本身添加侦听器,组件构造函数是在组件上添加事件侦听器的好地方。
constructor() {
super();
this.addEventListener('click', (e) => console.log(e.type, e.target.localName));
}
向组件本身添加事件侦听器是一种事件委托形式,可以减少代码或提高性能。
但是,当组件上的事件侦听器监听到事件触发时,从组件的 shadow DOM 触发的事件会被重定向。这意味着事件目标是组件本身。
重定向可能会干扰事件委托,为了避免这种情况,可以将事件侦听器添加到组件的影子根本身。由于shadowRoot中不可用,因此constructor可以在方法中添加事件侦听器createRenderRoot。
官方文档这样解释,确实有些难以理解,可以看一下下面的demo
export class BaseApp extends LitElement {
@property({ type: String })
name: string = "";
// 渲染组件
protected render() {
return html`
<div>
<button>点击</button>
<div>当前是:${this.name}</div>
</div>
`;
}
constructor() {
super();
this.addEventListener("click", (e: Event) => {
this.name = (e.target as Element).localName;
});
}
}
当在constructor
构造函数添加监听事件时,被监听的对象是组件本身
export class BaseApp extends LitElement {
@property({ type: String })
name: string = "";
// 渲染组件
protected render() {
return html`
<div>
<button>点击</button>
<div>当前是:${this.name}</div>
</div>
`;
}
protected createRenderRoot() {
const root = super.createRenderRoot();
root.addEventListener(
"click",
(e: Event) => (this.name = (e.target as Element).localName)
);
//必须返回节点
return root;
}
}
这时可以看到被监听的对象是触发点击事件的元素
正常情况下使用构造函数即可,注意使用箭头函数可以保证this的指向不会发生改变
将事件监听器添加到其他元素
如果您的组件将事件侦听器添加到除了它自己或其模板化的 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();
}
优化性能
添加事件侦听器的速度非常快,而且通常不是性能问题。但是,对于使用频率高且需要大量事件监听器的组件,可以通过事件委托减少使用的监听器数量,渲染后异步添加监听器来优化首次渲染性能。
事件委托
使用事件委托可以减少使用的事件侦听器的数量,从而提高性能。集中事件处理以减少代码有时也很方便。事件委托只能用于处理冒泡
可以在 DOM 中的任何祖先元素上听到冒泡事件。您可以通过在祖先组件上添加单个事件侦听器来利用这一点,以便在 DOM 中其任何后代调度的冒泡事件时收到通知。
@customElement('my-element')
class MyElement extends LitElement {
@property() clicked = '';
protected render() {
return html`
<div @click="${this._clickHandler}">
<button>Item 1</button>
<button>Item 2</button>
<button>Item 3</button>
</div>
<p>Clicked: ${this.clicked}</p>
`;
}
private _clickHandler(e: Event) {
this.clicked = e.target === e.currentTarget ?
'container' : (e.target as HTMLDivElement).textContent!;
}
}
异步添加事件处理器
要在渲染后添加事件侦听器,请使用firstUpdated方法。这是一个 Lit 生命周期回调,它在组件首次更新并呈现其模板化 DOM 后运行。
回调在firstUpdated你的组件第一次更新并调用它的render方法之后触发,但在浏览器有机会绘制之前。
为了确保在用户可以看到组件后添加侦听器,您可以等待浏览器绘制后解析的 Promise。
async firstUpdated() {
// Give the browser a chance to paint
await new Promise((r) => setTimeout(r, 0));
this.addEventListener('click', this._handleClick);
}
调度事件
所有 DOM 节点都可以使用该dispatchEvent方法调度事件。首先,创建一个事件实例,指定事件类型和选项。然后将其传递给dispatchEvent如下:
const event = new Event('my-event', {bubbles: true, composed: true});
myElement.dispatchEvent(event);
该bubbles选项允许事件沿 DOM 树向上流动到调度元素的祖先。如果您希望事件能够参与事件委托,设置此标志很重要。
设置该composed选项以允许在元素所在的影子 DOM 树之上调度事件很有用。
在vue中我们一般是通过自定义事件来实现子组件向父组件的通信,那么在lit中如何实现呢?
子组件
@customElement("demo-test")
export class DemoTest extends LitElement {
@property()
msg: string = "哈哈哈";
// 渲染组件
protected render() {
return html`
<div>
<button @click=${this.sendMessage}>发送消息</button>
<div>${this.msg}</div>
</div>
`;
}
sendMessage() {
const options = {
detail: this.msg, //自定义参数
bubbles: true,
composed: true,
};
this.dispatchEvent(new CustomEvent("getMessage", options));
}
}
父组件
import "./view/demo-test";
@customElement("base-app")
export class BaseApp extends LitElement {
@property({ type: String })
name: string = "";
// 渲染组件
protected render() {
return html`
<div>
父组件
<demo-test @getMessage=${this.printMessage}></demo-test>
</div>
`;
}
printMessage(data: any) {
console.log(data);
}
}
注:向父组件传递的参数只能放在detail
里
元素更新后调度事件
通常,只有在元素更新和渲染之后才应该触发事件。如果事件旨在基于用户交互传达呈现状态的变化,这可能是必要的。在这种情况下,组件的updateCompletePromise 可以在更改状态之后但在调度事件之前等待。
private async _notify() {
this.open = !this.open;
await this.updateComplete;
const name = this.open ? 'opened' : 'closed';
this.dispatchEvent(new CustomEvent(name, {bubbles: true, composed: true}));
}
可以根据实际情况来进行选择,如果不涉及元素更新直接使用调度事件即可;如果涉及元素更新,应在组件更新后在进行事件调度