1. 簡介
上次更新時間:2021 年 8 月 10 日
網頁元件
網頁元件是一組網路平台 API,可讓您建立新的自訂、可重複使用及封裝的 HTML 標記,以便在網頁和網頁應用程式中使用。根據 Web Component 標準建立的自訂元件和小工具,也能在各種新式瀏覽器上運作,並搭配任何支援 HTML 的 JavaScript 程式庫或架構使用。
什麼是 Lit?
Lit 是一種簡單的程式庫,可用來建構快速、輕量、可搭配任何架構,或完全沒有架構的網路元件。您可以利用 Lit 建構共用元件、應用程式、設計系統等。
Lit 提供的 API 可簡化常見的網頁元件工作,例如管理屬性、屬性和轉譯。
課程內容
- 什麼是網頁元件
- Web 元件的概念
- 如何建立網頁元件
- 什麼是 lit-html 和 LitElement
- 適用於網頁元件的 Lit 功能
建構項目
- 基本喜歡 / 不喜歡 Web 元件
- 喜歡 / 不喜歡的 Lit 型 Web 元件
軟硬體需求
- 任何更新版本瀏覽器 (Chrome、Safari、Firefox、Chromium Edge)。網頁元件適用於所有新式瀏覽器和 polyfill,可能適用於 Microsoft Internet Explorer 11 和非 Chromium Edge Microsoft Edge。
- 瞭解 HTML、CSS、JavaScript 和 Chrome 開發人員工具。
2. 開始設定與探索遊樂場
存取程式碼
本程式碼研究室中會提供 Lit Playground 的連結,如下所示:
Playground 是一個程式碼沙箱,可完全在瀏覽器中執行。這個程式庫可以編譯及執行 TypeScript 和 JavaScript 檔案,也可以自動解析匯入節點模組。例如:
// before
import './my-file.js';
import 'lit';
// after
import './my-file.js';
import 'https://unpkg.com/lit?module';
您可以在 Lit Playground 中進行整個教學課程,使用這些查核點作為起點。如果您使用 VS Code,就可以透過這些檢查點下載任何步驟的起始程式碼,並使用這些檢查點檢查您的工作。
探索燈光效果的遊樂場 UI
Lit Playground UI 螢幕截圖醒目顯示您將在這個程式碼研究室中使用的部分。
- 檔案選取器。注意加號按鈕...
- 檔案編輯器。
- 程式碼預覽。
- 「重新載入」按鈕。
- 下載按鈕。
VS Code 設定 (進階)
使用這項 VS Code 設定的優點如下:
- 範本類型檢查
- 範本智慧功能和自動完成建議
如果您已安裝 NPM、VS Code (含 lit-plugin 外掛程式),並瞭解如何使用該環境,只要下載並按照下列步驟啟動這些專案即可:
- 按下「下載」按鈕
- 將 tar 檔案的內容解壓縮至目錄
- 安裝可解析裸機模組指定碼的開發伺服器 (Lint 團隊建議採用 @web/dev-server)
- 以下是
package.json
範例。
- 以下是
- 執行開發伺服器並開啟瀏覽器 (如果您使用的是
@web/dev-server
,則可以使用npx web-dev-server --node-resolve --watch --open
)- 如果您要使用
package.json
範例,請使用npm run serve
- 如果您要使用
3. 定義自訂元素
自訂元素
網頁元件是 4 組原生網路 API 的集合。這些因素包括:
- ES 模組
- 自訂元素
- 陰影 DOM
- HTML 範本
您已使用 ES 模組規格,透過 <script type="module">
建立 JavaScript 模組,並採用匯入和匯出內容並載入至網頁中。
定義自訂元素
自訂元素規格可讓使用者使用 JavaScript 定義自己的 HTML 元素。名稱必須包含連字號 (-
),以便與原生瀏覽器元素有所區別。清除 index.js
檔案並定義自訂元素類別:
index.js
class RatingElement extends HTMLElement {}
customElements.define('rating-element', RatingElement);
定義自訂元素的方法是將擴充 HTMLElement
的類別與連字號標記名稱建立關聯。呼叫 customElements.define
會指示瀏覽器將 RatingElement
類別與 tagName ‘rating-element'
建立關聯。這表示文件中所有名為 <rating-element>
的元素都會與這個類別建立關聯。
將 <rating-element>
放入文件內文,並查看轉譯內容。
index.html
<body>
<rating-element></rating-element>
</body>
現在,只要檢視輸出內容,即可發現未轉譯任何內容。這是正常情況,因為您尚未指定瀏覽器如何顯示 <rating-element>
。您可以在 Chrome 開發人員工具中選取 <rating-element>
,確認自訂元素定義是否成功元素選取器後,在控制台中呼叫:
$0.constructor
輸出內容應會如下所示:
class RatingElement extends HTMLElement {}
自訂元素生命週期
自訂元素附有一組生命週期掛鉤。這些因素包括:
constructor
connectedCallback
disconnectedCallback
attributeChangedCallback
adoptedCallback
系統會在元素初次建立時呼叫 constructor
,例如呼叫 document.createElement(‘rating-element')
或 new RatingElement()
。建構函式很適合用來設定元素,但一般來說,在建構函式「啟動」元素的建構函式中進行 DOM 操控的做法錯誤,是不理想的做法效能因素
當自訂元素附加至 DOM 時,系統會呼叫 connectedCallback
。這通常是初始進行 DOM 操作的情況。
從 DOM 移除自訂元素後,會呼叫 disconnectedCallback
。
使用者指定的屬性有任何變更時,系統就會呼叫 attributeChangedCallback(attrName, oldValue, newValue)
。
透過 adoptNode
(例如在 HTMLTemplateElement
中) 從另一個 documentFragment
採用自訂元素至主要文件中時,系統會呼叫 adoptedCallback
。
算繪 DOM
現在,請返回自訂元素,並將某些 DOM 與該元素建立關聯。在元素附加至 DOM 時設定元素內容:
index.js
class RatingElement extends HTMLElement {
constructor() {
super();
this.rating = 0;
}
connectedCallback() {
this.innerHTML = `
<style>
rating-element {
display: inline-flex;
align-items: center;
}
rating-element button {
background: transparent;
border: none;
cursor: pointer;
}
</style>
<button class="thumb_down" >
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>
`;
}
}
customElements.define('rating-element', RatingElement);
在 constructor
中,您將名為 rating
的執行個體屬性儲存在元素上。在 connectedCallback
中,您要將 DOM 子項新增至 <rating-element>
,即可顯示目前評分,以及「喜歡」和「不喜歡」按鈕。
4. 陰影 DOM
為什麼要使用 Shadow DOM?
在上一步中,您會注意到插入樣式標記中的選取器,包括同時選取網頁上的任何評分元素和按鈕。這可能會導致樣式從元素外流,並選取您不想設定樣式的其他節點。此外,這個自訂元素外的其他樣式,可能會不小心設定自訂元素中節點的樣式。例如,嘗試在主要文件的標題中加入樣式標記:
index.html
<!DOCTYPE html>
<html>
<head>
<script src="./index.js" type="module"></script>
<style>
span {
border: 1px solid red;
}
</style>
</head>
<body>
<rating-element></rating-element>
</body>
</html>
輸出內容應在評分的跨度周圍有紅色邊框方塊。這是一個小案例,但缺少 DOM 封裝可能會導致更複雜的應用程式發生問題。這時 Shadow DOM 就能派上用場
連接陰影根
將陰影根附加至元素,並在該根層級轉譯 DOM:
index.js
class RatingElement extends HTMLElement {
constructor() {
super();
this.rating = 0;
}
connectedCallback() {
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<style>
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
</style>
<button class="thumb_down" >
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>
`;
}
}
customElements.define('rating-element', RatingElement);
重新整理頁面時,您會發現主要文件中的樣式無法再選取陰影根目錄中的節點。
這是如何辦到的?在 connectedCallback
中呼叫 this.attachShadow
,後者會將陰影根附加至元素。open
模式表示陰影內容可供檢查,並且透過 this.shadowRoot
存取陰影根目錄。請同時查看 Chrome 檢查器中的 Web 元件:
現在,您應該會看到保存內容的可展開陰影根目錄。該陰影根層級中的所有項目都稱為 Shadow DOM。如果您在 Chrome 開發人員工具中選取評分元素,並呼叫 $0.children
,會發現該元素不會傳回子項。這是因為 Shadow DOM 不算是同一個 DOM 樹狀結構與直接子項的一部分,而是「陰影樹」的一部分。
淺色 DOM
實驗:新增節點做為 <rating-element>
的直接子項:
index.html
<rating-element>
<div>
This is the light DOM!
</div>
</rating-element>
重新整理頁面後,就會發現這個自訂元素的「Light DOM」內未顯示這個新的 DOM 節點。這是因為 Shadow DOM 會透過 <slot>
元素,控制 Light DOM 節點如何投影至陰影區塊的功能。
5. HTML 範本
使用範本的好處
使用不含清理作業的 innerHTML
和範本常值字串可能導致指令碼插入出現安全性問題。雖然過去的方法包含使用 DocumentFragment
,但也有一些問題,例如範本定義時載入圖片、執行指令碼,以及造成重複使用的障礙。這就是 <template>
元素的來源。範本提供 inert DOM,這是複製節點的高效能方法,以及可重複使用的範本。
使用範本
接下來,將元件轉換到使用 HTML 範本:
index.html
<body>
<template id="rating-element-template">
<style>
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
</style>
<button class="thumb_down" >
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating"></span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>
</template>
<rating-element>
<div>
This is the light DOM!
</div>
</rating-element>
</body>
在這裡,您已將 DOM 內容移至主要文件 DOM 中的範本標記。現在重構自訂元素定義:
index.js
class RatingElement extends HTMLElement {
constructor() {
super();
this.rating = 0;
}
connectedCallback() {
const shadowRoot = this.attachShadow({mode: 'open'});
const templateContent = document.getElementById('rating-element-template').content;
const clonedContent = templateContent.cloneNode(true);
shadowRoot.appendChild(clonedContent);
this.shadowRoot.querySelector('.rating').innerText = this.rating;
}
}
customElements.define('rating-element', RatingElement);
如要使用這個範本元素,您必須查詢範本、取得其內容,然後使用 templateContent.cloneNode
複製這些節點,其中 true
引數會執行深層複製作業。接著,您要使用資料來初始化群數。
恭喜,您現已擁有 Web 元件!很抱歉,這個專區尚未執行任何動作,接下來請新增一些功能。
6. 新增功能
屬性繫結
如要在評分元素上設定評分,目前只能建構元素、在物件上設定 rating
屬性,然後將它放到網頁上。不過,這並不是原生 HTML 元素通常的運作方式。原生 HTML 元素通常會隨著屬性和屬性變更而更新。
加入下列程式碼,讓自訂元素在 rating
屬性變更時更新檢視畫面:
index.js
constructor() {
super();
this._rating = 0;
}
set rating(value) {
this._rating = value;
if (!this.shadowRoot) {
return;
}
const ratingEl = this.shadowRoot.querySelector('.rating');
if (ratingEl) {
ratingEl.innerText = this._rating;
}
}
get rating() {
return this._rating;
}
您將針對評分屬性新增 setter 和 getter,然後更新評分元素的文字 (如有)。也就是說,如果為元素設定評分屬性,檢視畫面就會更新:直接在開發人員工具控制台中完成簡單的測試!
屬性繫結
現在,請在屬性變更時更新檢視畫面。這類似於設定 <input value="newValue">
時更新其檢視畫面的輸入。幸好,Web 元件生命週期包含 attributeChangedCallback
。如要更新評分,請加入以下幾行:
index.js
static get observedAttributes() {
return ['rating'];
}
attributeChangedCallback(attributeName, oldValue, newValue) {
if (attributeName === 'rating') {
const newRating = Number(newValue);
this.rating = newRating;
}
}
為了讓 attributeChangedCallback
觸發,您必須為 RatingElement.observedAttributes which defines the attributes to be observed for changes
設定靜態 getter。接著,請在 DOM 中宣告評分。歡迎體驗:
index.html
<rating-element rating="5"></rating-element>
評分現在應透過宣告更新!
按鈕功能
現在,多半都缺少按鈕功能。這個元件的行為應讓使用者能夠提供上下投票,並向使用者提供視覺回饋。雖然您可以使用某些事件監聽器和反映屬性來實作這個項目,不過請先附加以下這行程式碼來更新樣式,提供視覺回饋:
index.html
<style>
...
:host([vote=up]) .thumb_up {
fill: green;
}
:host([vote=down]) .thumb_down {
fill: red;
}
</style>
在 Shadow DOM 中,:host
選取器是指 Shadow Root 所附加的節點或自訂元素。在此案例中,如果 vote
屬性為 "up"
,則「喜歡」按鈕會變為綠色,但如果 vote
為 "down", then it will turn the thumb-down button red
,現在,請透過類似實作 rating
的方式為 vote
建立反映屬性 / 屬性,藉此實作此邏輯。從屬性 setter 和 getter 開始:
index.js
constructor() {
super();
this._rating = 0;
this._vote = null;
}
set vote(newValue) {
const oldValue = this._vote;
if (newValue === oldValue) {
return;
}
if (newValue === 'up') {
if (oldValue === 'down') {
this.rating += 2;
} else {
this.rating += 1;
}
} else if (newValue === 'down') {
if (oldValue === 'up') {
this.rating -= 2;
} else {
this.rating -= 1;
}
}
this._vote = newValue;
this.setAttribute('vote', newValue);
}
get vote() {
return this._vote;
}
您在 constructor
中使用 null
初始化 _vote
執行個體屬性,並在 setter 中檢查新值是否不同。如果需要,請按照實際情況調整評分,此外,最重要的是將 vote
屬性反映給含有 this.setAttribute
的主機。
接下來,請設定屬性繫結:
index.js
static get observedAttributes() {
return ['rating', 'vote'];
}
attributeChangedCallback(attributeName, oldValue, newValue) {
if (attributeName === 'rating') {
const newRating = Number(newValue);
this.rating = newRating;
} else if (attributeName === 'vote') {
this.vote = newValue;
}
}
同樣,這與您使用 rating
屬性繫結時執行的程序相同。您在 observedAttributes
中加入 vote
,並在 attributeChangedCallback
中設定 vote
屬性。最後,加入一些點擊事件監聽器,提供按鈕功能!
index.js
constructor() {
super();
this._rating = 0;
this._vote = null;
this._boundOnUpClick = this._onUpClick.bind(this);
this._boundOnDownClick = this._onDownClick.bind(this);
}
connectedCallback() {
...
this.shadowRoot.querySelector('.thumb_up')
.addEventListener('click', this._boundOnUpClick);
this.shadowRoot.querySelector('.thumb_down')
.addEventListener('click', this._boundOnDownClick);
}
disconnectedCallback() {
this.shadowRoot.querySelector('.thumb_up')
.removeEventListener('click', this._boundOnUpClick);
this.shadowRoot.querySelector('.thumb_down')
.removeEventListener('click', this._boundOnDownClick);
}
_onUpClick() {
this.vote = 'up';
}
_onDownClick() {
this.vote = 'down';
}
在 constructor
中,您可以將某些點擊事件監聽器繫結至元素,並保留參照。在 connectedCallback
中,您可以監聽按鈕的點擊事件。在 disconnectedCallback
中,您會清理這些事件監聽器,而在點擊事件監聽器本身,您會正確設定 vote
。
恭喜,您已經擁有功能完整的 Web Component!試著點選一些按鈕!但目前的問題是,我的 JS 檔案現在佔了 96 行,HTML 檔案有 43 行,程式碼就相當冗長,無法滿足這類簡易元件的需求。這時 Google 的 Lit 專案就派上用場了!
7. Lit-html
程式碼查核點
為什麼要使用 lit-html
首先,<template>
標記既實用又效能,但不會與元件的邏輯包裝在一起,因此很難將範本和其他邏輯一起發布。此外,範本元素在本質上的使用方式與命令式程式碼有關,與宣告的程式設計模式相比,在多數情況下,這類程式碼的可讀性較低。
這時 lit-html 就能派上用場!Lit html 是 Lit 的轉譯系統,可讓您在 JavaScript 中編寫 HTML 範本,然後有效率地呈現和重新轉譯這些範本與資料以建立和更新 DOM。這與常見的 JSX 和 VDOM 程式庫類似,但前者是直接在瀏覽器中執行,在許多情況下也能更有效率。
使用 Lit HTML
接下來,請遷移原生 Web 元件 rating-element
,以便使用使用標記範本常值的 Lit 範本,這些函式會將範本字串做為具有特殊語法的引數。Lit 接著會使用內部的範本元素來快速轉譯,並提供部分安全性作業的防毒功能。首先,在網頁元件中新增 render()
方法,將 index.html
中的 <template>
遷移至 Lit 範本:
index.js
// Dont forget to import from Lit!
import {render, html} from 'lit';
class RatingElement extends HTMLElement {
...
render() {
if (!this.shadowRoot) {
return;
}
const template = html`
<style>
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
:host([vote=up]) .thumb_up {
fill: green;
}
:host([vote=down]) .thumb_down {
fill: red;
}
</style>
<button class="thumb_down">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button class="thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>`;
render(template, this.shadowRoot);
}
}
您也可以從「index.html
」中刪除範本。在這個轉譯方法中,您會定義名為 template
的變數,並叫用 html
標記的範本常值函式。您也會發現,您已使用 ${...}
的範本常值內插語法,在 span.rating
元素中執行簡單的資料繫結。這表示您最終不再需要強制更新該節點。此外,您也可以呼叫淺色的 render
方法,將範本同步算繪到陰影根目錄中。
改用宣告式語法
現在您已捨棄 <template>
元素,請重構程式碼,改為呼叫新定義的 render
方法。請先利用光照的事件監聽器繫結,清除事件監聽器程式碼:
index.js
<button
class="thumb_down"
@click=${() => {this.vote = 'down'}}>
...
<button
class="thumb_up"
@click=${() => {this.vote = 'up'}}>
Lit 範本可以使用 @EVENT_NAME 繫結語法將事件監聽器新增至節點。在此情況下,在此情況下,每次按下這些按鈕時,您都會更新 vote
屬性。
接著,清除 constructor
和 connectedCallback
和 disconnectedCallback
中的事件監聽器初始化程式碼:
index.js
constructor() {
super();
this._rating = 0;
this._vote = null;
}
connectedCallback() {
this.attachShadow({mode: 'open'});
this.render();
}
// remove disonnectedCallback and _onUpClick and _onDownClick
您能夠從全部三個回呼中移除點擊事件監聽器邏輯,甚至能完全移除 disconnectedCallback
!您也可以從 connectedCallback
中移除所有 DOM 初始化程式碼,讓它看起來更加優雅。此外,您也可以移除 _onUpClick
和 _onDownClick
事件監聽器方法!
最後,請更新屬性 setter 以使用 render
方法,以便在屬性或屬性變更時更新區塊:
index.js
set rating(value) {
this._rating = value;
this.render();
}
...
set vote(newValue) {
const oldValue = this._vote;
if (newValue === oldValue) {
return;
}
if (newValue === 'up') {
if (oldValue === 'down') {
this.rating += 2;
} else {
this.rating += 1;
}
} else if (newValue === 'down') {
if (oldValue === 'up') {
this.rating -= 2;
} else {
this.rating -= 1;
}
}
this._vote = newValue;
this.setAttribute('vote', newValue);
// add render method
this.render();
}
在這裡,您可以從 rating
setter 中移除 dom 更新邏輯,並從 vote
setter 新增對 render
的呼叫。現在,查看繫結和事件監聽器的套用位置,範本更容易閱讀。
重新整理頁面,即可看到可正常運作的評分按鈕,此按鈕在按下認同票數時看起來應該像這樣!
8. LitElement
為什麼要使用 LitElement
程式碼仍有一些問題。首先,如果變更 vote
屬性或屬性,可能會變更 rating
屬性,導致呼叫 render
兩次。雖然重複呼叫轉譯的功效大同小異,但 javascript VM 仍會花費時間呼叫該功能兩次。其次,新增屬性和屬性的過程相當繁瑣,因為需要大量的樣板程式碼。這時 LitElement
就能派上用場!
LitElement
是 Lit 的基礎類別,可讓您建立可在各種架構和環境中使用的快速輕量網頁元件。接下來,看看變更實作方式以便使用 LitElement
,瞭解 rating-element
可以在 rating-element
中有哪些功能!
使用 LitElement
首先,請從 lit
套件匯入 LitElement
基本類別並建立子類別:
index.js
import {LitElement, html, css} from 'lit';
class RatingElement extends LitElement {
// remove connectedCallback()
...
您匯入 LitElement
,是 rating-element
的新基礎類別。接下來,請保留 html
匯入作業,最後使用 css
,以便我們針對 CSS 數學、範本和其他功能定義 CSS 標記範本常值。
接著,將樣式從算繪方法移到 Lit 的靜態樣式表:
index.js
class RatingElement extends LitElement {
static get styles() {
return css`
:host {
display: inline-flex;
align-items: center;
}
button {
background: transparent;
border: none;
cursor: pointer;
}
:host([vote=up]) .thumb_up {
fill: green;
}
:host([vote=down]) .thumb_down {
fill: red;
}
`;
}
...
這裡大部分的風格都住在 Lit 中。Lit 將採用這些樣式,並使用「可建置的樣式表」等瀏覽器功能來加快轉譯速度,並且視需要在舊版瀏覽器中透過「網頁元件 polyfill」加以傳遞。
生命週期
Lit 在原生 Web 元件回呼之上,導入一組轉譯生命週期回呼方法。當宣告的 Lit 屬性變更時,就會觸發這些回呼。
如要使用這項功能,您必須以靜態方式宣告哪些屬性會觸發轉譯生命週期。
index.js
static get properties() {
return {
rating: {
type: Number,
},
vote: {
type: String,
reflect: true,
}
};
}
// remove observedAttributes() and attributeChangedCallback()
// remove set rating() get rating()
您可以在這裡定義 rating
和 vote
會觸發 LitElement 的轉譯生命週期,以及定義要用於將字串屬性轉換為屬性的類型。
<user-profile .name=${this.user.name} .age=${this.user.age}>
${this.user.family.map(member => html`
<family-member
.name=${member.name}
.relation=${member.relation}>
</family-member>`)}
</user-profile>
此外,vote
屬性上的 reflect
旗標也會自動更新您在 vote
setter 中手動觸發的主要元素 vote
屬性。
您已建立靜態屬性區塊,就可以移除所有屬性和屬性轉譯更新邏輯。這表示您可以移除下列方法:
connectedCallback
observedAttributes
attributeChangedCallback
rating
(設定器和 getter)vote
(setter 和 getter,但保留 setter 中的變更邏輯)
您要保留的內容是 constructor
,並新增 willUpdate
生命週期方法:
index.js
constructor() {
super();
this.rating = 0;
this.vote = null;
}
willUpdate(changedProps) {
if (changedProps.has('vote')) {
const newValue = this.vote;
const oldValue = changedProps.get('vote');
if (newValue === 'up') {
if (oldValue === 'down') {
this.rating += 2;
} else {
this.rating += 1;
}
} else if (newValue === 'down') {
if (oldValue === 'up') {
this.rating -= 2;
} else {
this.rating -= 1;
}
}
}
}
// remove set vote() and get vote()
在這裡,您只要初始化 rating
和 vote
,然後將 vote
setter 邏輯移至 willUpdate
生命週期方法即可。每當有更新的屬性變更時,系統都會在 render
之前呼叫 willUpdate
方法,因為 LitElement 會批次變更屬性,並且讓算繪作業非同步。變更 willUpdate
中的回應式屬性 (例如 this.rating
) 不會觸發不必要的 render
生命週期呼叫。
最後,render
是 LitElement 生命週期方法,需要我們「傳回」 Lit 範本:
index.js
render() {
return html`
<button
class="thumb_down"
@click=${() => {this.vote = 'down'}}>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z"/></svg>
</button>
<span class="rating">${this.rating}</span>
<button
class="thumb_up"
@click=${() => {this.vote = 'up'}}>
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewbox="0 0 24 24" width="24"><path d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z"/></svg>
</button>`;
}
您不必再檢查陰影根目錄,也不必再呼叫先前從 'lit'
套件匯入的 render
函式。
您的元素現在應該會顯示在預覽畫面中。就只要按一下!
9. 恭喜
恭喜,您已成功從頭開始建構 Web 元件,並將其發展為 LitElement!
Lit 非常小 (小於 5kb 壓縮 + gzip 壓縮),速度超級快,對編寫程式來說十分有趣!您可以讓其他架構使用元件,也可以用該元件建構出完善的應用程式!
現在您已經瞭解 Web 元件的定義、建構方式,以及 Lit 如何更輕鬆地建立 Web 元件!
程式碼查核點
是否要對照我們的最終程式碼?請按這裡來比較。
後續步驟
查看其他程式碼研究室!
其他資訊
- Lit 互動式教學課程
- Lit 文件
- Open Web 元件 - 社群執行指南和工具社群
- WebComponents.dev - 在所有已知架構中建立 Web 元件
社群
- Lit and Friends Slack - 最大的 Web 元件社群
- @buildWithLit on Twitter - Lit 製作團隊的 Twitter 帳戶
- SF Web 元件 - 舊金山的 Web 元件聚會