使用 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 基础知识上。
- 使用 Shadow DOM 在 MDN 上。
较旧的浏览器。在不支持原生 Shadow DOM 的较旧浏览器上,可以使用 Web 组件填充程序。请注意,Lit 的 polyfill-support
模块必须与 Web 组件填充程序一起加载。有关详细信息,请参阅 旧版浏览器需求。
访问 Shadow DOM 中的节点
访问 Shadow DOM 中的节点 的永久链接Lit 将组件渲染到其 renderRoot
,默认情况下,renderRoot
是一个 Shadow 根。要查找内部元素,您可以使用 DOM 查询 API,例如 this.renderRoot.querySelector()
。
renderRoot
始终应该是一个 Shadow 根或一个元素,它们共享 API,如 .querySelectorAll()
和 .children
。
您可以在组件初始渲染后(例如,在 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
承诺。
例如,如果 @queryAsync
返回的节点可能会由于另一个属性更改而更改,这将很有用。
使用插槽渲染子元素
使用插槽渲染子元素 的永久链接您的组件可能会接受子元素(例如 <ul>
元素可以具有 <li>
子元素)。
<my-element>
<p>A child</p>
</my-element>
默认情况下,如果元素有 Shadow 树,则其子元素根本不会渲染。
要渲染子元素,您的模板需要包含一个或多个 <slot>
元素,这些元素充当子节点的占位符。
使用 slot 元素
使用 slot 元素 的永久链接要渲染元素的子元素,请在元素的模板中为它们创建一个 <slot>
。子元素不会在 DOM 树中移动,但它们会像如果它们是 <slot>
的子元素一样被渲染。例如
使用命名插槽
使用命名插槽 的永久链接要将子元素分配给特定插槽,请确保子元素的 slot
属性与插槽的 name
属性匹配
命名插槽只接受具有匹配
slot
属性的子元素。例如,
<slot name="one"></slot>
仅接受具有slot="one"
属性的子元素。具有
slot
属性的子元素将仅渲染在具有匹配name
属性的插槽中。例如,
<p slot="one">...</p>
将仅放置在<slot name="one"></slot>
中。
指定插槽回退内容
指定插槽回退内容 的永久链接您可以为插槽指定回退内容。当没有子元素分配给插槽时,将显示回退内容。
<slot>I am fallback content</slot>
渲染回退内容。如果任何子节点被分配给插槽,则其回退内容不会渲染。没有名称的默认插槽接受任何子节点。即使唯一的分配节点是包含空格的文本节点,它也不会渲染回退内容,例如 <example-element> </example-element>
。在使用 Lit 表达式作为自定义元素的子元素时,请确保在适当的情况下使用非渲染值,以便渲染任何插槽回退内容。有关更多信息,请参阅 删除子元素内容。
访问插槽子元素
访问插槽子元素 的永久链接要访问分配给 Shadow 根中插槽的子元素,您可以使用标准 slot.assignedNodes
或 slot.assignedElements
方法以及 slotchange
事件。
例如,您可以创建一个 getter 来访问分配给特定插槽的元素
get _slottedChildren() {
const slot = this.shadowRoot.querySelector('slot');
return slot.assignedElements({flatten: true});
}
您还可以使用 slotchange
事件,当分配的节点更改时采取行动。以下示例提取所有插槽子元素的文本内容。
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.assignedElements
或 slot.assignedNodes
的结果。使用它们来查询分配给给定插槽的元素或节点。
两者都接受一个可选对象,该对象具有以下属性
属性 | 描述 |
---|---|
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
的默认实现将 shadowRootOptions
作为选项参数传递给 attachShadow
,用于创建组件的 Shadow 根。它可以设置为自定义 ShadowRootInit 字典中允许的任何选项,例如 mode
和 delegatesFocus
。
class DelegatesFocus extends LitElement {
static shadowRootOptions = {...LitElement.shadowRootOptions, delegatesFocus: true};
}
有关更多信息,请参阅 MDN 上的 Element.attachShadow()。
实现 createRenderRoot
实现 createRenderRoot 的永久链接 createRenderRoot
的默认实现创建了一个开放的阴影根,并向其中添加了 static styles
类字段中设置的任何样式。有关样式的更多信息,请参阅 样式。
要自定义组件的渲染根,请实现 createRenderRoot
并返回您希望模板渲染到的节点。
例如,要将模板渲染到主 DOM 树中作为元素的子元素,请实现 createRenderRoot
并返回 this
。
渲染到子元素中。 通常不建议渲染到子元素而不是阴影 DOM。您的元素将无法访问 DOM 或样式范围,也无法将元素组合到其内部 DOM 中。