使用 Shadow DOM
Lit 元件使用 shadow DOM 來封裝它們的 DOM。Shadow DOM 提供了一種將獨立且封裝的 DOM 樹新增至元素的方法。DOM 封裝是解鎖與頁面上運作的任何其他程式碼(包括其他 Web 元件或 Lit 元件)互通性的關鍵。
Shadow DOM 提供三個優點
- DOM 範圍設定。像
document.querySelector
這樣的 DOM API 不會找到元件 shadow DOM 中的元素,因此全域腳本更難意外地中斷您的元件。 - 樣式範圍設定。您可以為您的 shadow DOM 撰寫封裝的樣式,這些樣式不會影響 DOM 樹的其餘部分。
- 組合。元件的 shadow 根(包含其內部 DOM)與元件的子元素分離。您可以選擇如何在元件的內部 DOM 中渲染子元素。
有關 shadow DOM 的詳細資訊
- Shadow DOM v1:獨立的 Web 元件 在 Web Fundamentals 上。
- 使用 shadow DOM 在 MDN 上。
較舊的瀏覽器。 在原生 shadow DOM 不可用的較舊瀏覽器上,可以使用 web components polyfills。請注意,Lit 的 polyfill-support
模組必須與 web components polyfills 一起載入。請參閱 傳統瀏覽器的需求 以了解詳細資訊。
存取 shadow DOM 中的節點
“存取 shadow DOM 中的節點” 的永久連結Lit 將元件渲染到其 renderRoot
,預設情況下是 shadow 根。若要尋找內部元素,您可以使用 DOM 查詢 API,例如 this.renderRoot.querySelector()
。
renderRoot
應始終為 shadow 根或元素,它們共享類似 .querySelectorAll()
和 .children
的 API。
您可以在元件初始渲染之後(例如,在 firstUpdated
中)查詢內部 DOM,或使用 getter 模式
firstUpdated() {
this.staticNode = this.renderRoot.querySelector('#static-node');
}
get _closeButton() {
return this.renderRoot.querySelector('#close-button');
}
LitElement 提供一組裝飾器,可提供定義此類 getter 的簡寫方式。
@query、@queryAll 和 @queryAsync 裝飾器
“@query、@queryAll 和 @queryAsync 裝飾器” 的永久連結@query
、@queryAll
和 @queryAsync
裝飾器都提供了一種便捷的方式來存取內部元件 DOM 中的節點。
使用裝飾器。 裝飾器是一個提議的 JavaScript 功能,因此您需要使用像 Babel 或 TypeScript 這樣的編譯器來使用裝飾器。請參閱 使用裝飾器 以了解詳細資訊。
@query
“@query” 的永久連結修改類別屬性,將其變成一個 getter,該 getter 會從渲染根傳回一個節點。當為 true 時,可選的第二個參數僅執行一次 DOM 查詢並快取結果。當被查詢的節點不會更改時,這可以用作效能最佳化。
import {LitElement, html} from 'lit';
import {query} from 'lit/decorators/query.js';
class MyElement extends LitElement {
@query('#first')
_first;
render() {
return html`
<div id="first"></div>
<div id="second"></div>
`;
}
}
此裝飾器等效於
get _first() {
return this.renderRoot?.querySelector('#first') ?? null;
}
@queryAll
“@queryAll” 的永久連結與 query
相同,但傳回所有相符的節點,而不是單個節點。它等效於呼叫 querySelectorAll
。
import {LitElement, html} from 'lit';
import {queryAll} from 'lit/decorators/queryAll.js';
class MyElement extends LitElement {
@queryAll('div')
_divs;
render() {
return html`
<div id="first"></div>
<div id="second"></div>
`;
}
}
在此,_divs
會傳回模板中的兩個 <div>
元素。對於 TypeScript,@queryAll
屬性的類型為 NodeListOf<HTMLElement>
。如果您確切知道要檢索哪種節點,則類型可以更具體
@queryAll('button')
_buttons!: NodeListOf<HTMLButtonElement>
buttons
後面的驚嘆號 (!
) 是 TypeScript 的 非空斷言運算符。它會告訴編譯器將 buttons
視為始終已定義,而不是 null
或 undefined
。
@queryAsync
“@queryAsync” 的永久連結與 @query
類似,但它不是直接傳回節點,而是傳回一個 Promise
,該 Promise
在任何待處理的元素渲染完成後解析為該節點。程式碼可以使用它,而不是等待 updateComplete
promise。
舉例來說,如果 @queryAsync
傳回的節點可能會因另一個屬性變更而變更,則此功能很有用。
使用 slots 渲染子元素
“使用 slots 渲染子元素” 的永久連結您的元件可以接受子元素(如 <ul>
元素可以有 <li>
子元素)。
<my-element>
<p>A child</p>
</my-element>
預設情況下,如果元素有 shadow 樹,則其子元素根本不會渲染。
若要渲染子元素,您的範本需要包含一個或多個 <slot>
元素,它們充當子節點的預留位置。
使用 slot 元素
“使用 slot 元素” 的永久連結若要渲染元素的子元素,請在元素的範本中為它們建立一個 <slot>
。子元素並未在 DOM 樹中移動,但它們的渲染就像它們是 <slot>
的子元素一樣。例如
使用具名 slots
“使用具名 slots” 的永久連結若要將子元素指派給特定 slot,請確保子元素的 slot
屬性與 slot 的 name
屬性相符
具名 slots 僅接受具有相符
slot
屬性的子元素。例如,
<slot name="one"></slot>
僅接受具有屬性slot="one"
的子元素。具有
slot
屬性的子元素只會渲染到具有相符name
屬性的 slot 中。例如,
<p slot="one">...</p>
只會放置在<slot name="one"></slot>
中。
指定 slot 的回退內容
“指定 slot 的回退內容” 的永久連結您可以為 slot 指定回退內容。當沒有子元素指派給 slot 時,會顯示回退內容。
<slot>I am fallback content</slot>
渲染回退內容。 如果有任何子節點指派給 slot,則不會渲染其回退內容。沒有名稱的預設 slot 接受任何子節點。即使唯一指派的節點是包含空白字元的文字節點,它也不會渲染回退內容,例如 <example-element> </example-element>
。當使用 Lit 表達式作為自訂元素的子元素時,請確保在適當的時候使用不渲染的值,以便渲染任何 slot 回退內容。請參閱 移除子內容 以了解詳細資訊。
存取 slotted 的子元素
“存取 slotted 的子元素” 的永久連結若要存取 shadow 根中指派給 slots 的子元素,您可以使用標準的 slot.assignedNodes
或 slot.assignedElements
方法和 slotchange
事件。
例如,您可以建立一個 getter 來存取特定 slot 的已指派元素
get _slottedChildren() {
const slot = this.shadowRoot.querySelector('slot');
return slot.assignedElements({flatten: true});
}
您也可以使用 slotchange
事件,以便在指派的節點變更時採取動作。以下範例會擷取所有 slotted 子元素的文字內容。
handleSlotchange(e) {
const childNodes = e.target.assignedNodes({flatten: true});
// ... do something with childNodes ...
this.allText = childNodes.map((node) => {
return node.textContent ? node.textContent : ''
}).join('');
}
render() {
return html`<slot @slotchange=${this.handleSlotchange}></slot>`;
}
如需詳細資訊,請參閱 MDN 上的 HTMLSlotElement。
@queryAssignedElements 和 @queryAssignedNodes 裝飾器
“@queryAssignedElements 和 @queryAssignedNodes 裝飾器” 的永久連結@queryAssignedElements
和 @queryAssignedNodes
會將類別屬性轉換為 getter,該 getter 會傳回分別在元件 shadow 樹中給定 slot 上呼叫 slot.assignedElements
或 slot.assignedNodes
的結果。使用這些來查詢指派給給定 slot 的元素或節點。
兩者都接受具有以下屬性的可選物件
屬性 | 描述 |
---|---|
flatten | 布林值,指定是否要將指定的節點扁平化,方法是將任何子 <slot> 元素替換為其指定的節點。 |
slot | 插槽名稱,指定要查詢的插槽。保留為未定義則選取預設插槽。 |
selector (僅限 queryAssignedElements ) | 如果指定,則僅傳回符合此 CSS 選擇器的指定元素。 |
決定要使用哪個裝飾器取決於您要查詢指定給插槽的文字節點,還是僅查詢元素節點。此決定取決於您的使用案例。
使用裝飾器。 裝飾器是一個提議的 JavaScript 功能,因此您需要使用像 Babel 或 TypeScript 這樣的編譯器來使用裝飾器。請參閱 使用裝飾器 以了解詳細資訊。
@queryAssignedElements({slot: 'list', selector: '.item'})
_listItems!: Array<HTMLElement>;
@queryAssignedNodes({slot: 'header', flatten: true})
_headerNodes!: Array<Node>;
上面的範例等同於下列程式碼
get _listItems() {
const slot = this.shadowRoot.querySelector('slot[name=list]');
return slot.assignedElements().filter((node) => node.matches('.item'));
}
get _headerNodes() {
const slot = this.shadowRoot.querySelector('slot[name=header]');
return slot.assignedNodes({flatten: true});
}
自訂渲染根
「自訂渲染根」的永久連結每個 Lit 元件都有一個渲染根,這是一個 DOM 節點,作為其內部 DOM 的容器。
預設情況下,LitElement 會建立一個開放的 shadowRoot
並在其中渲染,產生下列 DOM 結構
<my-element>
#shadow-root
<p>child 1</p>
<p>child 2</p>
有兩種方法可以自訂 LitElement 使用的渲染根
- 設定
shadowRootOptions
。 - 實作
createRenderRoot
方法。
設定 shadowRootOptions
「設定 shadowRootOptions」的永久連結 自訂渲染根最簡單的方法是設定 shadowRootOptions
靜態屬性。createRenderRoot
的預設實作會在建立元件的 shadow root 時,將 shadowRootOptions
作為選項引數傳遞給 attachShadow
。它可以設定為自訂 ShadowRootInit 字典中允許的任何選項,例如 mode
和 delegatesFocus
。
class DelegatesFocus extends LitElement {
static shadowRootOptions = {...LitElement.shadowRootOptions, delegatesFocus: true};
}
請參閱 MDN 上的 Element.attachShadow() 以取得更多資訊。
實作 createRenderRoot
「實作 createRenderRoot」的永久連結 createRenderRoot
的預設實作會建立一個開放的 shadow root,並將 static styles
類別欄位中設定的任何樣式新增至其中。如需樣式的詳細資訊,請參閱樣式。
若要自訂元件的渲染根,請實作 createRenderRoot
並傳回您希望範本渲染到的節點。
例如,若要將範本渲染到主 DOM 樹狀結構中作為元素的子節點,請實作 createRenderRoot
並傳回 this
。
渲染到子節點中。 一般不建議渲染到子節點而非 shadow DOM 中。您的元素將無法存取 DOM 或樣式範圍,並且無法將元素組合到其內部 DOM 中。