給 Polymer 使用者的 Lit

— 更新於
Photo of Arthur Evans
Arthur Evans

Lit 是 Polymer 函式庫的後繼者。如果您的專案是使用 Polymer 建置的,並且想要將其遷移到 Lit,或者您熟悉 Polymer 並想了解它與 Lit 的比較,那麼這份文件非常適合您。

本文件快速概述了 Lit 與 Polymer 的關聯,並提供了一個實用指南,展示了常見的 Polymer 程式碼如何轉換為 Lit。

Polymer 是最早用於建置網頁元件的函式庫之一。Lit 是 Polymer 的後繼者,由同一個團隊建置,並具有許多相同的目標。這兩個專案有許多共同的目標,但 Lit 利用了 Polymer 開發過程中吸取的經驗。

因為 Polymer 是 Lit 的前身,所以兩者之間有很多相似之處。這兩個函式庫都讓建置像內建 HTML 元素一樣運作的元件變得容易,而且都具有宣告式的 HTML 範本。

Lit 在幾個方面與 Polymer 不同

  • Lit 的渲染預設為非同步且批次處理。 除了少數例外,所有 Polymer 更新都是同步的。

  • Lit 公開了一個更新生命週期,提供了一個強大的機制,用於觀察屬性的變更並從中計算出衍生值。 Polymer 具有宣告式的觀察者和計算屬性,但很難預測觀察者的執行順序。

  • Lit 專注於以 JavaScript 為優先的撰寫方式,使用原生 JavaScript 模組。 Polymer *最初*專注於以 HTML 為優先的撰寫方式,這是由 HTML Imports 規範所實現的,但該規範已從網頁平台中移除。

  • Lit 運算式使用標準 JavaScript。 Polymer 在其綁定中使用有限的領域特定語言。因為 Lit 使用標準 JavaScript,您也可以在運算式中使用 JavaScript 來進行控制流程(條件範本和重複範本),而 Polymer 使用專門的輔助元素。

  • Lit 的目標是建立一個簡單、易於理解的心理模型,並具有單向資料流。 Polymer 支援雙向資料綁定和觀察者,這在簡單的專案中可能非常好用,但隨著專案變得更加複雜,往往會讓資料流的推理變得更加困難。

如果您計劃從 Polymer 遷移到 Lit,您不必一次完成所有作業。Polymer 和 Lit 可以協同工作,因此您可以一次遷移一個元件。

在開始使用 Lit 之前,您需要做一些事情來更新您的專案

  • 將您的專案更新到 Polymer 3。

  • 確保您的專案工具支援較新的 JavaScript 功能。

  • 移除雙向綁定。

Polymer 2.x 和更早版本使用 HTML imports,這些 import 已從網頁平台中移除。Polymer 3 和 Lit 都以 JavaScript 模組的形式發布,這意味著它們可以很好地協同工作,並且可以利用各種現代網頁工具。

在大多數情況下,大多數遷移過程可以使用 Polymer 模組化工具自動化。如需更多資訊,請參閱Polymer 3.0 升級指南

Polymer 使用了 ECMAScript 2015 版本的 JavaScript 規格中的功能。如果您是從其中一個 Polymer 入門套件開始的,您的工具鏈可能不支援較新的 JavaScript。

Lit 使用了 ECMAScript 2019 的功能(而且某些 Lit 範例程式碼可能包含較新的語言功能)。如果您的工具無法處理這些較新的 JavaScript 功能,您需要更新您的工具。例如

  • 公用實例欄位和公用靜態欄位。這些在範例程式碼中廣泛使用

  • Async 和 await,可用於簡化基於 Promise 的程式碼。例如

如需更多關於 Lit 的語言需求資訊,請參閱需求

Polymer 的雙向綁定有效地將主機屬性與子屬性緊密耦合。這種緊密耦合會產生許多問題,因此團隊選擇不將雙向綁定新增至 Lit。

在遷移到 Lit 之前移除雙向綁定將減少您的遷移複雜性,並讓您在開始遷移之前,在沒有雙向綁定的情況下在 Polymer 中測試您的應用程式。

如果您的應用程式未使用雙向綁定,您可以跳過此章節。

對於雙向綁定,Polymer 使用其自己的協定,該協定具有三個主要元件

  • 從主機到子系的綁定。

  • 處理來自子元素的屬性變更事件的事件監聽器。

  • 一個讓子元素在屬性變更時自動觸發變更事件的工具(notify: true)。

最後一項是最有問題的。元件會針對屬性的任何變更觸發變更事件,而且每個變更事件都會同步處理。在整個應用程式中廣泛使用雙向綁定會使得很難推斷資料流以及元件自我更新的順序。

理想情況下,事件是傳送來傳達不明顯的顯式變更的離散訊號。將事件作為設定屬性的副作用來傳送(如 Polymer 所做的那樣)可能會使通訊變得多餘且隱含。特別是這種隱含行為可能會讓資料流難以理解。

為了總結自訂元素最佳實務指南,元件應該觸發事件

  • 當元素的狀態因使用者互動而變更時,例如在元件內按一下按鈕或編輯文字欄位。

  • 當元件內部的某些內容變更時,例如計時器觸發或動畫完成。

理想情況下,元件應該觸發語意事件,該事件描述變更的內容,而不是讓低階 UI 事件冒泡。例如,讓使用者更新個人資料的表單可能會在使用者按一下完成按鈕時觸發 profile-updated 事件。profile-updated 事件與父系相關:按一下事件則不然。

有很多方法可以取代雙向綁定。如果您只是想將現有的 Polymer 應用程式遷移到 Lit,您可能只想使用與雙向綁定具有相似功能的程式碼來取代它

  • 移除自動屬性變更事件(notify: true)。

  • 使用更具意圖的變更事件取代自動屬性變更事件。

  • 使用單向綁定和事件處理常式取代雙向綁定註解。(此步驟是選用的,但可簡化轉換至 Lit。)

例如,如果子系因為使用者互動而更新通知屬性,請移除 notify: true,並根據使用者互動事件手動觸發事件變更事件。

請考慮以下 Polymer 程式碼

此元件使用與 input 元素的雙向綁定,並且具有通知屬性,因此父系元素可以使用與 name 屬性的雙向綁定。與輸入的雙向綁定很有意義,因為只有在觸發 input 事件時才會設定綁定。但是,透過新增 input 事件的事件監聽器,您可以消除通知屬性

此程式碼會明確監聽輸入事件並觸發 name-changed 事件。此程式碼將與預期 Polymer 雙向綁定協定的父系搭配運作,因此您可以一次更新一個元件。

當父系設定 name 屬性時,此程式碼不會觸發屬性變更事件,這是件好事。而且您可以相當直接地將此程式碼遷移到 Lit。

使用像這樣的事件處理常式,您也可以將邏輯新增至子系元件,例如僅在使用者停止輸入一段時間後才觸發 name-changed 事件。

單向資料流。

另一種替代方法是使用單向資料流模式(使用像 Redux 這樣的狀態容器)取代雙向綁定。個別元件可以訂閱狀態更新,並派發動作來更新狀態。我們建議將此用於新開發,但如果您已經有基於雙向綁定的應用程式,則可能需要更多的工作。

本節說明 Polymer 程式碼如何處理常見任務,並顯示等效的 Lit 程式碼。如果您已經熟悉 Polymer 並想學習 Lit,或者如果您要將現有專案從 Polymer 遷移到 Lit,這可能會很有幫助。

本節假設您正在使用 Polymer 3.0。如果您要將專案從 Polymer 遷移到 Lit,您會想要先遷移到 Polymer 3.0

如需更多關於遷移現有專案的特定資訊,請參閱從 Polymer 遷移至 Lit

JavaScript 或 TypeScript?

Lit 兩者都能很好地搭配使用。大多數 Lit 範例都會使用可切換的程式碼範例小工具顯示,因此您可以選取 TypeScript 或 JavaScript 語法。

Polymer 和 Lit 都以網頁元件為基礎,因此在這兩個函式庫中定義元件看起來非常相似。在最簡單的情況下,唯一的區別是基底類別。

Polymer

Lit

Lit 提供了一組裝飾器,可以改善您的開發體驗,例如前一節 TypeScript 範例中顯示的 customElement。請注意,本網站上的 TypeScript 範例包含裝飾器,而 JavaScript 範例則省略了它們,但您實際上可以使用 JavaScript 搭配 裝飾器,或使用 TypeScript 不使用 裝飾器。這取決於您。在 JavaScript 中使用裝飾器需要像 Babel 這樣的編譯器。(由於您已經需要一個 TypeScript 的編譯器,您只需要配置它來處理裝飾器。) 如需更多資訊,請參閱 裝飾器

Polymer 3 和 Lit 都提供了用於定義範本的 html 標籤函數。

Polymer

Lit

請注意,Lit 的 html 與 Polymer 的 html 不同。它們具有相同的基本目的,但它們的工作方式不同。Polymer 的 html 在元素初始化期間被呼叫一次。Lit 的 html 通常在每次更新期間被呼叫。設定工作在每個模板字串中執行一次,因此後續對 Lit 的 html 函數進行增量更新的呼叫速度非常快。

如需有關 Lit 範本的更多資訊,請參閱 範本概觀

Polymer 元件通常會直接在範本中包含樣式。

在 Lit 中,您通常會使用 css 標籤函數在靜態 styles 欄位中提供樣式。

也支援像在 Polymer 中一樣,直接在範本中添加樣式標籤。

使用樣式標籤的效能可能略低於靜態 styles 欄位,因為樣式是針對每個實例評估一次,而不是針對每個類別評估一次。

如需更多資訊,請參閱 樣式

Polymer 有資料綁定,而 Lit 在其範本中具有表達式。Lit 表達式是綁定到範本中特定位置的標準 JavaScript 表達式。因此,Lit 表達式幾乎可以完成您可以使用 Polymer 資料綁定執行的所有操作,以及許多您在 Polymer 中無法輕易執行的操作。

雙向綁定。

Lit 團隊刻意選擇不實作雙向資料綁定。雖然此功能似乎簡化了一些常見的需求,但實際上很難推斷資料流以及元件更新自身的順序。我們建議在遷移到 Lit 之前移除雙向綁定。如需更多資訊,請參閱 移除雙向綁定

Polymer

Lit

如您所見,主要差異是取代了 Polymer 綁定周圍的雙括號

[[表達式]]

使用了標籤範本字串的表達式語法

${表達式}

另請注意,Lit 表達式使用 this.name 而不是僅使用 name。Polymer 綁定僅允許在綁定註釋中使用某些內容,例如屬性名稱或路徑(例如 user.idshapes[4].type)。屬性名稱或路徑是相對於目前的 綁定範圍 評估的。

您可以在 Lit 中使用任何標準 JavaScript 表達式,並且適用標準 JavaScript 範圍。例如,您可以使用在 render() 方法內建立的本機變數,或使用 this 存取實例變數。

與 Polymer 一樣,Lit 支援使用表達式設定屬性、屬性和事件處理程序。Lit 使用稍微不同的語法,使用前綴而不是後綴。下表總結了 Polymer 和 Lit 綁定語法之間的差異

類型PolymerLit
屬性屬性名稱=[[]].propertyName=${}
屬性屬性名稱$=[[]]屬性名稱=${}
布林屬性屬性名稱?=[[]]?屬性名稱=${}
事件on-事件名稱$=[[處理程序]]@事件名稱=${處理程序}

註解

  • 屬性表達式。Lit 使用字面屬性名稱,並帶有句點前綴。Polymer 使用對應的 屬性名稱

  • 事件處理程序。在 Lit 中,處理程序可以是方法,例如 ${this.clickHandler} 或箭頭函數。使用箭頭函數,您可以關閉其他資料或使用不同的簽名呼叫函數。例如

如需更多資訊,請參閱 表達式

一般而言,我們建議在將 Polymer 專案遷移到 Lit 之前移除雙向綁定。如需更多資訊,請參閱 移除雙向綁定

如果您已將雙向綁定替換為單向綁定和事件監聽器,您應該可以將它們直接遷移到 Lit。

如果您要編寫 Lit 元件來取代過去使用雙向綁定與 Polymer 子元件通訊的父元件,請新增屬性表達式來設定屬性,以及事件監聽器來處理 property-changed 事件。

Polymer

Lit

Polymer 使用 dom-if 輔助元素支援條件式。

在 Lit 中,您可以使用 JavaScript 條件表達式。條件運算子 (或三元運算子) 效果很好

與 Polymer dom-if 不同,條件運算子可讓您提供 true 和 false 條件的內容,儘管您也可以傳回空字串來不呈現任何內容,如範例所示。

如需更多資訊,請參閱 條件式

預設情況下,Polymer 的 dom-if 行為與 Lit 條件式略有不同。當條件從 truthy 值變為 falsy 值時,dom-if 只會隱藏條件式 DOM,而不是將其從 DOM 樹狀結構中移除。當條件再次變為 truthy 時,這可能會節省一些資源。

將 Polymer dom-if 遷移到 Lit 時,您有幾種選擇

  • 使用簡單的 JavaScript 條件式。當條件變為 falsy 時,Lit 會移除並捨棄條件式 DOM。如果您將 restamp 屬性設定為 truedom-if 會執行相同的操作。

  • 使用標準 hidden 屬性來隱藏內容,而不會將其從頁面中移除。

    這非常輕量。但是,即使條件為 false,也會在首次呈現時建立 DOM。

  • 將條件式包裝在 cache 指令中,以避免在條件變更時捨棄並重新建立 DOM。

在大多數情況下,簡單的條件式效果很好。如果條件式 DOM 很大且複雜,並且您在條件切換為 true 時觀察到重新建立 DOM 的延遲,您可以使用 Lit 的 cache 指令來保留條件式 DOM。使用 cache 時,DOM 仍然會從樹狀結構中移除,但會快取在記憶體中,這可以在條件變更時節省資源。

由於這不會在條件為 falsy 時呈現任何內容,因此您可以使用它來避免在初始頁面載入時建立複雜的 DOM。

Polymer 使用 dom-repeat 輔助程式來重複範本。

dom-repeat 內部的範本可以存取有限的一組屬性:主機元素上的屬性,以及 dom-repeat 新增的 itemindex 屬性。

與條件式一樣,Lit 可以使用 JavaScript 處理重複,方法是讓表達式傳回值陣列。Lit 的 map 指令的工作方式類似於 map() 陣列方法,不同之處在於它接受其他類型的可迭代物件,例如集合或產生器。

雖然 Polymer 綁定只能存取某些屬性,但 Lit 表達式可以存取 JavaScript 範圍中可用的任何內容。

您也可以在 render() 方法中產生陣列

如需更多資訊,請參閱 清單,或試試我們關於 使用清單 的互動式教學課程。

處理來自重複範本的事件

有多種方法可以將事件監聽器新增至重複的元素

  • 如果事件冒泡,您可以使用事件委派,將單個監聽器新增至父元素。

  • 如果事件不冒泡,您可以將監聽器新增至每個重複的元素。

如需使用這兩種技術的範例,請參閱事件章節中的 監聽從重複元素觸發的事件

在處理來自重複範本所產生元素的事件時,您通常需要識別觸發事件的元素,以及產生該元素的資料。

Polymer 透過 將資料新增至事件物件 來協助後者。

Lit 不會新增此額外資料,但您可以將唯一值附加到每個重複的元素,以便於參考

當將監聽器新增至個別項目時,您也可以使用箭頭函數將資料直接傳遞至事件處理程序

Lit 的反應屬性與 Polymer 的宣告屬性非常匹配。反應屬性支援與宣告屬性相同的許多功能,包括在屬性和屬性之間同步值。

與 Polymer 一樣,Lit 可讓您使用靜態 properties 欄位來設定屬性。

Lit 也支援使用 @property 裝飾器來宣告反應屬性。如需更多資訊,請參閱 關於裝飾器

Polymer

Lit

Polymer 和 Lit 在宣告屬性時都支援許多選項。以下清單顯示了 Polymer 選項及其 Lit 對等選項。

type

Lit 的 type 選項具有相同的用途。

value

Lit 不支援像這樣設定屬性的值。相反地,在建構函式中設定預設值。如果使用 @property 裝飾器,您也可以使用類別欄位初始設定式。

reflectToAttribute

在 Lit 中,此選項已縮短為 reflect

readOnly

Lit 不包含對唯讀反應屬性的任何內建支援。如果您需要讓計算屬性成為元件公用 API 的一部分,您可以新增沒有設定器的 getter。

notify

此功能用於支援雙向綁定。它沒有在 Lit 中實作,因為 雙向綁定的問題 中描述的問題。

Lit 元件可以使用原生 Web API (例如 dispatchEvent) 來觸發事件,以回應使用者輸入或內部狀態變更。

computed

Lit 不支援宣告式的計算屬性。請參閱計算屬性以了解替代方案。

觀察器

Lit 不直接支援觀察器。相反地,它在生命週期中提供了一些覆寫點,您可以在這些點根據已變更的任何屬性採取動作。

請注意,Polymer 範例使用 getter—static get properties()—而 Lit JavaScript 範例使用類別欄位—static properties。這兩種形式的工作方式相同。當 Polymer 3 發佈時,對類別欄位的支援並不普及,因此 Polymer 範例程式碼使用 getter。

如果您要將 Lit 元件新增至現有的 Polymer 應用程式,您的工具鏈可能不支援類別欄位格式。在這種情況下,您可以改用靜態 getter。

與 Polymer 一樣,Lit 在屬性變更時會執行髒檢查,以避免執行不必要的工作。如果您有一個持有物件或陣列的屬性,這可能會導致問題。如果您變更物件或陣列,Lit 將不會偵測到變更。

在大多數情況下,避免這些問題的最佳方法是使用不可變的資料模式,以便您始終指派新的物件或陣列值,而不是變更現有的物件或陣列。Polymer 的情況通常也是如此。

Polymer 包括用於可觀察地設定子屬性變更陣列的 API,但它們在正確使用方面有些挑戰。如果您正在使用這些 API,您可能需要遷移到不可變的資料模式。

如需更多資訊,請參閱變更物件和陣列屬性

唯讀屬性在 Polymer 中並非非常常用的功能,如果需要實作它們並不困難,因此它們未包含在 Lit API 中。

若要將內部狀態值公開為元件公共 API 的一部分,您可以新增一個沒有 setter 的 getter。您可能還希望在狀態變更時觸發事件(例如,從網路載入資源的元件可能具有「載入中」狀態,並在該狀態變更時觸發事件)。

Polymer 的唯讀屬性包含一個隱藏的 setter。如果這對您的元件有意義,您可以為您的屬性新增一個私有 setter。

請注意,如果屬性未包含在元件的範本中,則您不需要宣告它(使用 @propertystatic properties)或呼叫 requestUpdate()

Lit 提供許多可覆寫的生命週期方法,這些方法會在反應式屬性變更時呼叫。使用這些方法來實作在 Polymer 中的計算屬性或觀察器中會使用的邏輯。

以下列表總結了如何遷移不同種類的計算屬性和觀察器。

計算屬性

  • 對於在範本中短暫使用的值,請在 render() 中計算值作為局部變數,並在傳回之前在範本中使用它們。

  • 對於需要持久儲存或計算成本很高的值,請在 willUpdate() 中執行此操作。

  • 使用 changedProperties.has() 方法僅在依賴項變更時進行計算,以避免每次更新都進行昂貴的重新計算。

觀察器

  • 如果觀察器需要根據屬性變更直接作用於 DOM,請使用 updated()。這會在 render() 回呼之後呼叫。

  • 否則,請使用 willUpdate()

  • 在這兩種情況下,都使用 changedProperties.has() 來了解屬性何時變更。

基於路徑的觀察器

  • 這些是複雜的觀察器,會觀察像 foo.barfoo.* 這樣的路徑

  • 此功能非常特定於 Polymer 的資料系統,而 Lit 中沒有等效的功能。

  • 我們建議使用不可變的模式作為一種更具互通性的方式來傳達深層屬性的變更。如需更多資訊,請參閱變更物件和陣列屬性

如果您需要計算僅用於渲染的瞬態值,您可以在 render() 方法中直接計算它們。

Polymer

Lit

此外,由於 Lit 允許您在範本中使用任何 JavaScript 表達式,Polymer 的計算繫結可以用內聯表達式(包括函式呼叫)取代。

Polymer

Lit

由於 Lit 表達式是純 JavaScript,您需要在表達式內使用 this 來存取實例屬性或方法。

willUpdate() 回呼是根據其他屬性值計算屬性的理想位置。willUpdate() 接收已變更屬性值的映射,因此您可以處理目前的變更。

使用 willUpdate() 可讓您根據完整的已變更屬性集選擇要執行的操作。這可以避免多個觀察器或計算屬性以不可預測的方式交互作用的問題。

Mixins 是封裝可重複使用功能以在 Polymer 元件中使用的方式之一。如果您要將 Polymer mixin 移植到 Lit,您有幾個選項。

  • 獨立函式。由於 Polymer 的資料繫結只能存取實例成員,因此人們通常會建立 mixin,只是為了使函式可在資料繫結中使用。這在 Lit 中不是必需的,因為您可以在範本中使用任何 JavaScript 表達式。您可以從另一個模組匯入函式,並直接在範本中使用它,而不是使用 mixin。

  • Lit mixins。Lit mixins 的工作方式與 Polymer mixins 非常相似,因此許多 Polymer mixins 可以重新實作為 Lit mixins。如需更多資訊,請參閱Mixins

  • 反應式控制器。反應式控制器是封裝可重複使用功能的另一種方式。如需比較 mixins 與反應式控制器的更多資訊,請參閱控制器和 mixins

Lit 元件與 Polymer 元件具有相同的一組標準 Web 元件生命週期回呼。

此外,Lit 元件還有一組可用於自訂反應式更新週期的回呼。

如果您在 Polymer 元素中使用 ready() 回呼,您可以使用 Lit 的 firstUpdated() 回呼來達到相同的目的。firstUpdated() 回呼會在元件的 DOM 第一次渲染後呼叫。例如,如果您想聚焦渲染的 DOM 中的元素,則可以使用它。

如需更多資訊,請參閱完成更新