生命週期
Lit 元件使用標準的自訂元素生命週期方法。此外,Lit 還引入了響應式更新週期,在響應式屬性更改時將更改渲染到 DOM。
標準自訂元素生命週期
“標準自訂元素生命週期” 的永久連結Lit 元件是標準自訂元素,並繼承自訂元素的生命週期方法。有關自訂元素生命週期的資訊,請參閱 MDN 上的 使用生命週期回呼。
如果您需要自訂任何標準自訂元素生命週期方法,請務必呼叫 super
實作(例如 super.connectedCallback()
),以維持標準的 Lit 功能。
constructor()
“constructor()” 的永久連結在建立元素時呼叫。此外,當現有元素升級時也會呼叫,這會在自訂元素的定義在元素已位於 DOM 中之後載入時發生。
Lit 行為
“Lit 行為” 的永久連結使用 requestUpdate()
方法請求非同步更新,因此當 Lit 元件升級時,它會立即執行更新。
儲存已在元素上設定的所有屬性。這確保在升級之前設定的值得以保留,並正確覆寫元件設定的預設值。
使用案例
“使用案例” 的永久連結執行必須在第一次 更新之前完成的一次性初始化任務。例如,當不使用裝飾器時,屬性的預設值可以在建構子中設定,如在靜態屬性欄位中宣告屬性中所示。
constructor() {
super();
this.foo = 'foo';
this.bar = 'bar';
}
connectedCallback()
“connectedCallback()” 的永久連結當元件新增至文件的 DOM 時呼叫。
Lit 行為
“Lit 行為” 的永久連結Lit 會在元素連線後啟動第一個元素更新週期。為了準備渲染,Lit 也會確保建立 renderRoot
(通常是其 shadowRoot
)。
一旦元素至少連線到文件一次,無論元素的連線狀態如何,元件更新都會繼續進行。
使用案例
“使用案例” 的永久連結在 connectedCallback()
中,您應該設定僅在元素連線到文件時才應發生的任務。其中最常見的是將事件監聽器新增至元素外部的節點,例如新增至視窗的 keydown 事件處理常式。通常,在 connectedCallback()
中完成的任何操作都應該在元素斷開連線時取消 — 例如,移除視窗上的事件監聽器以防止記憶體洩漏。
connectedCallback() {
super.connectedCallback()
window.addEventListener('keydown', this._handleKeydown);
}
disconnectedCallback()
“disconnectedCallback()” 的永久連結當元件從文件的 DOM 中移除時呼叫。
Lit 行為
“Lit 行為” 的永久連結暫停響應式更新週期。當元素連線時會恢復。
使用案例
“使用案例” 的永久連結此回呼是元素可能不再使用的主要信號;因此,disconnectedCallback()
應該確保沒有任何內容持有對元素的參照(例如新增至元素外部節點的事件監聽器),以便可以將其進行垃圾收集。因為元素可能會在斷開連線後重新連線(例如在 DOM 或快取中移動元素的情況下),因此任何此類參照或監聽器可能都需要透過 connectedCallback()
重新建立,以便元素在這些情況下繼續按預期運作。例如,移除元素外部節點的事件監聽器,例如新增至視窗的 keydown 事件處理常式。
disconnectedCallback() {
super.disconnectedCallback()
window.removeEventListener('keydown', this._handleKeydown);
}
無需移除內部事件監聽器。 您無需移除在元件自己的 DOM 上新增的事件監聽器 — 包括在樣板中以宣告方式新增的事件監聽器。與外部事件監聽器不同,這些不會阻止元件被垃圾回收。
attributeChangedCallback()
“attributeChangedCallback()” 的永久連結當元素的 observedAttributes
之一變更時呼叫。
Lit 行為
“Lit 行為” 的永久連結Lit 使用此回呼將屬性中的變更同步到響應式屬性。具體而言,當設定屬性時,會設定對應的屬性。Lit 也會自動設定元素的 observedAttributes
陣列以符合元件的響應式屬性清單。
使用案例
“使用案例” 的永久連結您很少需要實作此回呼。
adoptedCallback()
“adoptedCallback()” 的永久連結當元件移動到新文件時呼叫。
請注意,adoptedCallback
未進行 polyfill。
Lit 行為
“Lit 行為” 的永久連結Lit 對此回呼沒有預設行為。
使用案例
“使用案例” 的永久連結只有在元素行為應該在變更文件時變更的高階使用案例中才應使用此回呼。
響應式更新週期
“響應式更新週期” 的永久連結除了標準自訂元素生命週期之外,Lit 元件還實作了響應式更新週期。
當響應式屬性變更或明確呼叫 requestUpdate()
方法時,會觸發響應式更新週期。Lit 會非同步執行更新,因此會批次處理屬性變更 — 如果在請求更新之後、更新開始之前有更多屬性變更,則所有變更都會擷取在同一次更新中。
更新發生在微任務計時時,這表示它們會在瀏覽器繪製下一個畫面到螢幕之前發生。請參閱 Jake Archibald 的文章,了解有關瀏覽器計時的更多資訊。
從高階層面來說,響應式更新週期為
- 當一個或多個屬性變更或呼叫
requestUpdate()
時,會排定更新。 - 更新會在繪製下一個畫面之前執行。
- 設定反映屬性。
- 呼叫元件的 render 方法以更新其內部 DOM。
- 更新完成,並解析
updateComplete
promise。
更詳細地說,它看起來像這樣
更新前

更新

更新後

changedProperties 映射
“changedProperties 映射” 的永久連結許多響應式更新方法都會收到變更屬性的 Map
。Map
金鑰是屬性名稱,其值是先前屬性值。您可以使用 this.property
或 this[property]
隨時找到目前的屬性值。
changedProperties 的 TypeScript 類型
“changedProperties 的 TypeScript 類型” 的永久連結如果您使用 TypeScript,並且想要對 changedProperties
映射進行強型別檢查,您可以使用 PropertyValues<this>
,它會推斷每個屬性名稱的正確類型。
import {LitElement, html, PropertyValues} from 'lit';
...
shouldUpdate(changedProperties: PropertyValues<this>) {
...
}
如果您不太關心強型別 — 或者您只檢查屬性名稱,而不是先前的值 — 您可以使用限制較少的類型,例如 Map<string, any>
。
請注意,PropertyValues<this>
無法辨識 protected
或 private
屬性。如果您要檢查任何 protected
或 private
屬性,則需要使用限制較少的類型。
在更新期間變更屬性
“在更新期間變更屬性” 的永久連結在更新期間(包括 render()
方法)變更屬性會更新 changedProperties
映射,但不會觸發新的更新。在 render()
之後(例如在 updated()
方法中)變更屬性會觸發新的更新週期,並且變更的屬性會新增到新的 changedProperties
映射中,以用於下一個週期。
觸發更新
連結到「觸發更新」當響應式屬性變更或呼叫 requestUpdate()
方法時,會觸發更新。由於更新是以非同步方式執行,因此在執行更新之前發生的任何和所有變更只會導致單次更新。
hasChanged()
連結到「hasChanged()」當設定響應式屬性時呼叫。預設情況下,hasChanged()
會執行嚴格相等檢查,如果它返回 true
,則會排定更新。有關更多資訊,請參閱設定 hasChanged()
。
requestUpdate()
連結到「requestUpdate()」呼叫 requestUpdate()
以排定明確的更新。當您需要元素在與屬性無關的事項變更時更新和渲染,這會很有用。例如,計時器元件可能會每秒呼叫 requestUpdate()
。
connectedCallback() {
super.connectedCallback();
this._timerInterval = setInterval(() => this.requestUpdate(), 1000);
}
disconnectedCallback() {
super.disconnectedCallback();
clearInterval(this._timerInterval);
}
已變更的屬性清單會儲存在 changedProperties
映射中,該映射會傳遞到後續的生命週期方法。映射的鍵是屬性名稱,而值是先前的屬性值。
選擇性地,您可以在呼叫 requestUpdate()
時傳遞屬性名稱和先前的值,這些值將儲存在 changedProperties
映射中。如果您為屬性實作自訂的 getter 和 setter,這會很有用。有關實作自訂 getter 和 setter 的更多資訊,請參閱響應式屬性。
this.requestUpdate('state', this._previousState);
執行更新
連結到「執行更新」執行更新時,會呼叫 performUpdate()
方法。此方法會呼叫多個其他生命週期方法。
在元件更新期間發生的任何通常會觸發更新的變更,都不會排定新的更新。這樣做的目的是為了可以在更新過程中計算屬性值。在更新期間變更的屬性會反映在 changedProperties
映射中,因此後續的生命週期方法可以根據這些變更採取行動。
shouldUpdate()
連結到「shouldUpdate()」呼叫以判斷是否需要更新週期。
引數 | changedProperties :Map ,其鍵是已變更屬性的名稱,而值是相應的先前值。 |
更新 | 否。在此方法內部的屬性變更不會觸發元素更新。 |
呼叫 super? | 沒有必要。 |
在伺服器上呼叫? | 否。 |
如果 shouldUpdate()
返回 true
(預設情況下會返回 true
),則更新會正常進行。如果它返回 false
,則不會呼叫其餘的更新週期,但 updateComplete
Promise 仍然會解析。
您可以實作 shouldUpdate()
來指定哪些屬性變更應導致更新。使用 changedProperties
的映射來比較目前和先前的值。
shouldUpdate(changedProperties: Map<string, any>) {
// Only update element if prop1 changed.
return changedProperties.has('prop1');
}
shouldUpdate(changedProperties) {
// Only update element if prop1 changed.
return changedProperties.has('prop1');
}
willUpdate()
連結到「willUpdate()」在 update()
之前呼叫,以計算更新期間所需的值。
引數 | changedProperties :Map ,其鍵是已變更屬性的名稱,而值是相應的先前值。 |
更新? | 否。在此方法內部的屬性變更不會觸發元素更新。 |
呼叫 super? | 沒有必要。 |
在伺服器上呼叫? | 是。 |
實作 willUpdate()
以計算依賴其他屬性並在更新過程的其餘部分中使用的屬性值。
willUpdate(changedProperties: PropertyValues<this>) {
// only need to check changed properties for an expensive computation.
if (changedProperties.has('firstName') || changedProperties.has('lastName')) {
this.sha = computeSHA(`${this.firstName} ${this.lastName}`);
}
}
render() {
return html`SHA: ${this.sha}`;
}
willUpdate(changedProperties) {
// only need to check changed properties for an expensive computation.
if (changedProperties.has('firstName') || changedProperties.has('lastName')) {
this.sha = computeSHA(`${this.firstName} ${this.lastName}`);
}
}
render() {
return html`SHA: ${this.sha}`;
}
update()
連結到「update()」呼叫以更新元件的 DOM。
引數 | changedProperties :Map ,其鍵是已變更屬性的名稱,而值是相應的先前值。 |
更新? | 否。在此方法內部的屬性變更不會觸發元素更新。 |
呼叫 super? | 是。如果沒有 super 呼叫,元素的屬性和樣板將不會更新。 |
在伺服器上呼叫? | 否。 |
將屬性值反映到屬性,並呼叫 render()
以更新元件的內部 DOM。
一般來說,您不需要實作此方法。
render()
連結到「render()」由 update()
呼叫,應該實作以返回可渲染的結果(例如 TemplateResult
),用於渲染元件的 DOM。
引數 | 無。 |
更新? | 否。在此方法內部的屬性變更不會觸發元素更新。 |
呼叫 super? | 沒有必要。 |
在伺服器上呼叫? | 是。 |
render()
方法沒有引數,但通常會參照元件屬性。有關更多資訊,請參閱渲染。
render() {
const header = `<header>${this.header}</header>`;
const content = `<section>${this.content}</section>`;
return html`${header}${content}`;
}
完成更新
連結到「完成更新」在呼叫 update()
以渲染元件 DOM 的變更後,您可以使用這些方法對元件的 DOM 執行操作。
firstUpdated()
連結到「firstUpdated()」在第一次更新元件的 DOM 後立即呼叫,在呼叫updated()
之前。
引數 | changedProperties :Map ,其鍵是已變更屬性的名稱,而值是相應的先前值。 |
更新? | 是。此方法內部的屬性變更會排定新的更新週期。 |
呼叫 super? | 沒有必要。 |
在伺服器上呼叫? | 否。 |
實作 firstUpdated()
以在建立元件的 DOM 後執行一次性工作。一些範例可能包括將焦點放在特定渲染的元素上,或將ResizeObserver或IntersectionObserver新增至元素。
firstUpdated() {
this.renderRoot.getElementById('my-text-area').focus();
}
updated()
連結到「updated()」每當元件的更新完成,並且元素的 DOM 已更新和渲染時呼叫。
引數 | changedProperties :Map ,其鍵是已變更屬性的名稱,而值是相應的先前值。 |
更新? | 是。此方法內部的屬性變更會觸發元素更新。 |
呼叫 super? | 沒有必要。 |
在伺服器上呼叫? | 否。 |
實作 updated()
以在更新後執行使用元素 DOM 的工作。例如,執行動畫的程式碼可能需要測量元素 DOM。
updated(changedProperties: Map<string, any>) {
if (changedProperties.has('collapsed')) {
this._measureDOM();
}
}
updated(changedProperties) {
if (changedProperties.has('collapsed')) {
this._measureDOM();
}
}
updateComplete
連結到「updateComplete」當元素完成更新時,updateComplete
promise 會解析。使用 updateComplete
等待更新。解析值是一個布林值,表示元素是否已完成更新。如果更新週期完成後沒有待處理的更新,則它將為 true
。
當元素更新時,它也可能會導致其子元素更新。預設情況下,updateComplete
promise 會在元素的更新完成時解析,但不會等待任何子元素完成其更新。可以透過覆寫getUpdateComplete
來自訂此行為。
有幾種情況需要知道元素的更新何時完成
測試 在編寫測試時,您可以在對元件的 DOM 進行斷言之前等待
updateComplete
promise。如果斷言依賴於元件整個後代樹狀結構的更新完成,則等待requestAnimationFrame
通常是更好的選擇,因為 Lit 的預設排程使用瀏覽器的微任務佇列,該佇列會在動畫畫面之前清空。這可確保頁面上所有待處理的 Lit 更新在requestAnimationFrame
回呼之前完成。測量 某些元件可能需要測量 DOM 才能實作某些版面配置。雖然始終最好使用純 CSS 而不是基於 JavaScript 的測量來實作版面配置,但有時 CSS 的限制會使其無法避免。在非常簡單的情況下,如果您要測量 Lit 或 ReactiveElement 元件,則在狀態變更之後且在測量之前等待
updateComplete
可能就足夠了。但是,由於updateComplete
不會等待所有後代的更新,因此我們建議使用ResizeObserver
作為一種更可靠的方式,在版面配置變更時觸發測量程式碼。事件 從元件發送事件的最佳實務是在渲染完成後,以便事件的監聽器看到元件的完整渲染狀態。若要執行此操作,您可以在觸發事件之前等待
updateComplete
promise。async _loginClickHandler() {
this.loggedIn = true;
// Wait for `loggedIn` state to be rendered to the DOM
await this.updateComplete;
this.dispatchEvent(new Event('login'));
}
如果在更新週期中發生未處理的錯誤,updateComplete
promise 會拒絕。有關更多資訊,請參閱處理更新週期中的錯誤。
處理更新週期中的錯誤
連結到「處理更新週期中的錯誤」如果您的生命週期方法(如 render()
或 update()
)中存在未捕獲的例外狀況,則會導致 updateComplete
promise 拒絕。如果您的生命週期方法中有可能擲回例外狀況的程式碼,最好將其放在 try
/catch
陳述式中。
如果您正在等待 updateComplete
promise,您也可能想要使用 try
/catch
try {
await this.updateComplete;
} catch (e) {
/* handle error */
}
在某些情況下,程式碼可能會在意外的地方擲回。作為後備,您可以新增 window.onunhandledrejection
的處理程式來捕獲這些問題。例如,您可以使用此回報錯誤給後端服務,以幫助診斷難以重現的問題。
window.onunhandledrejection = function(e) {
/* handle error */
}
實作額外自訂
連結到「實作其他自訂」本節涵蓋了一些用於自訂更新週期的較不常見的方法。
scheduleUpdate()
連結到「scheduleUpdate()」覆寫 scheduleUpdate()
以自訂更新的時機。scheduleUpdate()
在即將執行更新時呼叫,預設情況下它會立即呼叫 performUpdate()
。覆寫它以延遲更新 — 這項技術可用於解除鎖定主要渲染/事件執行緒。
例如,下列程式碼會將更新排程在下一個框架繪製後發生,如果更新很耗費資源,這可以減少 jank
protected override async scheduleUpdate(): Promise<void> {
await new Promise((resolve) => setTimeout(resolve));
super.scheduleUpdate();
}
async scheduleUpdate() {
await new Promise((resolve) => setTimeout(resolve));
super.scheduleUpdate();
}
如果您覆寫 scheduleUpdate()
,則您有責任呼叫 super.scheduleUpdate()
來執行待處理的更新。
非同步函數可選。
此範例顯示一個非同步函數,它隱含地返回一個 promise。您也可以將 scheduleUpdate()
寫成一個明確地返回 Promise
的函數。在任一種情況下,下一次更新要等到 scheduleUpdate()
返回的 promise 解析後才會開始。
performUpdate()
連結到「performUpdate()」實作響應式更新週期,呼叫其他方法,如 shouldUpdate()
、update()
和 updated()
。
呼叫 performUpdate()
以立即處理待處理的更新。這通常不需要,但在您需要同步更新時,可以在極少數情況下完成。(如果沒有待處理的更新,您可以呼叫 requestUpdate()
,然後呼叫 performUpdate()
來強制同步更新。)
使用 scheduleUpdate()
自訂排程。
如果您想要自訂更新的排程方式,請覆寫 scheduleUpdate()
。先前,我們建議為此目的覆寫 performUpdate()
。這仍然有效,但會使呼叫 performUpdate()
來同步處理待處理的更新變得更加困難。
hasUpdated
連結到「hasUpdated」如果元件至少更新過一次,則 hasUpdated
屬性會返回 true。您可以在任何生命週期方法中使用 hasUpdated
,僅在元件尚未更新時執行工作。
getUpdateComplete()
連結到「getUpdateComplete()」若要在實現 updateComplete
promise 之前等待其他條件,請覆寫 getUpdateComplete()
方法。例如,等待子元素的更新可能會很有用。首先等待 super.getUpdateComplete()
,然後等待任何後續狀態。
建議覆寫 getUpdateComplete()
方法,而不是 updateComplete
getter,以確保與使用 TypeScript 的 ES5 輸出的使用者相容(請參閱 TypeScript#338)。
class MyElement extends LitElement {
async getUpdateComplete() {
const result = await super.getUpdateComplete();
await this._myChild.updateComplete;
return result;
}
}
外部生命週期掛鉤:控制器和裝飾器
連結到「外部生命週期掛鉤:控制器和裝飾器」除了實作生命週期回呼的元件類別之外,外部程式碼(例如裝飾器)可能也需要掛鉤到元件的生命週期中。
Lit 提供兩個概念,讓外部程式碼與響應式更新生命週期整合:static addInitializer()
和 addController()
static addInitializer()
連結到 “static addInitializer()”addInitializer()
允許可以存取 Lit 類別定義的程式碼,在類別的實例被建構時執行程式碼。
這在撰寫自訂裝飾器時非常有用。裝飾器在類別定義時執行,並且可以執行像是取代欄位和方法定義之類的操作。如果它們也需要在建立實例時執行工作,它們必須呼叫 addInitializer()
。通常會使用此方法來新增一個 反應式控制器,以便裝飾器可以掛鉤到元件的生命週期中。
// A TypeScript decorator
const myDecorator = (proto: ReactiveElement, key: string) => {
const ctor = proto.constructor as typeof ReactiveElement;
ctor.addInitializer((instance: ReactiveElement) => {
// This is run during construction of the element
new MyController(instance);
});
};
// A Babel "Stage 2" decorator
const myDecorator = (descriptor) => {
...descriptor,
finisher(ctor) {
ctor.addInitializer((instance) => {
// This is run during construction of the element
new MyController(instance);
});
},
};
裝飾一個欄位將會導致每個實例執行一個初始化程式,該初始化程式會新增一個控制器。
class MyElement extends LitElement {
@myDecorator foo;
}
初始化程式是每個建構子儲存的。將初始化程式新增到子類別不會將其新增到父類別。由於初始化程式是在建構子中執行的,因此初始化程式會按照類別階層的順序執行,從父類別開始,然後進展到實例的類別。
addController()
連結到 “addController()”addController()
會將一個反應式控制器新增到 Lit 元件,以便該元件調用控制器的生命週期回呼。有關更多資訊,請參閱 反應式控制器 文件。
removeController()
連結到 “removeController()”removeController()
會移除一個反應式控制器,使其不再接收來自此元件的生命週期回呼。
伺服器端響應式更新週期
連結到 “伺服器端反應式更新週期”Lit 的伺服器端渲染套件目前正在積極開發中,因此以下資訊可能會變更。
當在伺服器上渲染 Lit 時,並非所有的更新週期都會被調用。以下方法會在伺服器上被調用。
