事件
事件是元件溝通變更的標準方式。這些變更通常因使用者互動而發生。例如,當使用者點擊按鈕時,按鈕會派發 click 事件;當使用者在輸入框中輸入值時,輸入框會派發 change 事件。
除了這些自動派發的標準事件之外,Lit 元件還可以派發自訂事件。例如,選單元件可能會派發事件以指示選取的項目已變更;彈出視窗元件可能會在彈出視窗開啟或關閉時派發事件。
任何 JavaScript 程式碼(包括 Lit 元件本身)都可以監聽事件並根據事件採取動作。例如,工具列元件可能會在選取選單項目時篩選列表;登入元件可能會在處理登入按鈕的點擊時處理登入。
監聽事件
“監聽事件”的永久連結除了標準的 addEventListener
API 之外,Lit 還引入了一種宣告式方法來新增事件監聽器。
在元件模板中新增事件監聽器
“在元件模板中新增事件監聽器”的永久連結您可以使用模板中的 @
表達式,將事件監聽器新增到元件模板中的元素。宣告式事件監聽器會在渲染模板時新增。
自訂事件監聽器選項
“自訂事件監聽器選項”的永久連結如果您需要自訂宣告式事件監聽器使用的事件選項(如 passive
或 capture
),您可以使用 @eventOptions
裝飾器在監聽器上指定這些選項。傳遞給 @eventOptions
的物件會作為 addEventListener
的 options
參數傳遞。
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>`
}
將事件監聽器新增到元件或其陰影根
“將事件監聽器新增到元件或其陰影根”的永久連結若要接收從元件的 slotted 子元素以及透過元件模板渲染到陰影 DOM 中的子元素派發的事件通知,您可以使用標準的 addEventListener
DOM 方法將監聽器新增到元件本身。請參閱 MDN 上的 EventTarget.addEventListener() 以了解完整詳細資訊。
元件建構子是將事件監聽器新增到元件的理想位置。
constructor() {
super();
this.addEventListener('click', (e) => console.log(e.type, e.target.localName));
}
將事件監聽器新增到元件本身是一種事件委派的形式,可以透過這種方式減少程式碼或提高效能。請參閱事件委派以了解詳細資訊。通常,當執行此操作時,會使用事件的 target
屬性根據觸發事件的元素來執行動作。
但是,從元件的陰影 DOM 觸發的事件會在元件上的事件監聽器聽到時重新定位。這表示事件目標是元件本身。請參閱在陰影 DOM 中使用事件以了解更多資訊。
重新定位可能會干擾事件委派,為了避免這種情況,可以將事件監聽器新增到元件的陰影根本身。由於 shadowRoot
在 constructor
中不可用,因此可以在 createRenderRoot
方法中新增事件監聽器,如下所示。請注意,務必從 createRenderRoot
方法中傳回陰影根。
將事件監聽器新增到其他元素
“將事件監聽器新增到其他元素”的永久連結如果您的元件將事件監聽器新增到除了自身或其模板化的 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();
}
請參閱 MDN 上關於使用自訂元素的生命週期回呼文件,以了解關於 connectedCallback
和 disconnectedCallback
的更多資訊。
效能優化
“效能優化”的永久連結新增事件監聽器非常快速,通常不是效能問題。但是,對於高頻率使用且需要大量事件監聽器的元件,您可以透過 事件委派減少使用的監聽器數量,並在渲染後非同步地新增監聽器,來優化首次渲染效能。
事件委派
“事件委派”的永久連結使用事件委派可以減少使用的事件監聽器數量,進而提高效能。有時集中處理事件也很方便,以減少程式碼。事件委派只能用於處理 bubble
的事件。請參閱派發事件,以了解關於 bubble 的詳細資訊。
可以在 DOM 中的任何上層元素上聽到 bubble 事件。您可以利用這一點,在上層元件上新增單一事件監聽器,以接收 DOM 中其任何子代派發的 bubble 事件的通知。使用事件的 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
選項對於允許事件在元素所在的陰影 DOM 樹之上分派非常有用。
請參閱在陰影 DOM 中使用事件以獲取更多資訊。
請參閱 MDN 上的 EventTarget.dispatchEvent() 以獲取關於分派事件的完整描述。
何時派發事件
連結到「何時分派事件」的永久連結事件應在回應使用者互動或元件狀態的非同步變更時分派。它們通常不應在回應元件擁有者透過其屬性或屬性 API 所做的狀態變更時分派。這通常是原生網頁平台元素的工作方式。
例如,當使用者在 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);
在陰影 DOM 中使用事件
連結到「在陰影 DOM 中使用事件」的永久連結當使用陰影 DOM 時,標準事件系統有一些重要的修改需要理解。陰影 DOM 的存在主要是為了在 DOM 中提供一個作用域機制,封裝關於這些「陰影」元素的詳細資訊。因此,陰影 DOM 中的事件會封裝來自外部 DOM 元素的一些詳細資訊。
理解合成事件派發
連結到「理解 composed 事件分派」的永久連結預設情況下,在陰影根目錄內分派的事件在陰影根目錄之外是不可見的。若要使事件通過陰影 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
,則分派事件的元素的所有祖先(包括外部陰影根目錄中的祖先)都可以接收到它。如果事件是 composed
但不會 bubble
,則只能在分派事件的元素和包含陰影根目錄的 host 元素上接收到它。
請注意,大多數標準使用者介面事件(包括所有滑鼠、觸控和鍵盤事件)都是會冒泡且是 composed 的。請參閱 關於 composed 事件的 MDN 文件以獲取更多資訊。
理解事件重新定位
連結到「理解事件重新定位」的永久連結從陰影根目錄內分派的Composed 事件會被重新定位,這表示對於任何在主控陰影根目錄的元素或其任何祖先上的監聽器,這些事件看起來像是來自主控元素。由於 Lit 元件會渲染到陰影根目錄中,因此從 Lit 元件內部分派的所有 composed 事件看起來都像是由 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。此方法會傳回事件分派所遍歷的所有節點的陣列,包括陰影根目錄內的節點。由於這會破壞封裝,因此應小心避免依賴可能暴露的實作細節。常見的使用案例包括判斷點擊的元素是否為錨點標籤,以用於用戶端路由。
handleMyEvent(event) {
console.log('Origin: ', event.composedPath()[0]);
}
請參閱關於 composedPath 的 MDN 文件以獲取更多資訊。
事件派發器與監聽器之間的溝通
連結到「事件分派器和監聽器之間的溝通」的永久連結事件主要用於傳達從事件分派器到事件監聽器的變更,但事件也可以用於將資訊從監聽器傳回給分派器。
一種方法是在事件上公開 API,讓監聽器可以使用它來自訂元件行為。例如,監聽器可以在自訂事件的 detail 屬性上設定屬性,然後由分派元件使用它來自訂行為。
另一種在分派器和監聽器之間溝通的方法是透過 preventDefault()
方法。可以呼叫它來指示不應發生事件的標準動作。當監聽器呼叫 preventDefault()
時,事件的 defaultPrevented
屬性會變成 true。然後,監聽器可以使用此標誌來自訂行為。
以下範例中使用了這兩種技術。