使用 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 的较旧浏览器上,可以使用 Web 组件填充程序。请注意,Lit 的 polyfill-support 模块必须与 Web 组件填充程序一起加载。有关详细信息,请参阅 旧版浏览器需求

Lit 将组件渲染到其 renderRoot,默认情况下,renderRoot 是一个 Shadow 根。要查找内部元素,您可以使用 DOM 查询 API,例如 this.renderRoot.querySelector()

renderRoot 始终应该是一个 Shadow 根或一个元素,它们共享 API,如 .querySelectorAll().children

您可以在组件初始渲染后(例如,在 firstUpdated 中)查询内部 DOM,或者使用 getter 模式

LitElement 提供了一组装饰器,这些装饰器提供了一种简短的方法来定义像这样的 getter。

@query、@queryAll 和 @queryAsync 装饰器

@query、@queryAll 和 @queryAsync 装饰器的永久链接

@query@queryAll@queryAsync 装饰器都提供了一种便捷的方法来访问内部组件 DOM 中的节点。

使用装饰器。装饰器是提议的 JavaScript 功能,因此您需要使用像 Babel 或 TypeScript 这样的编译器来使用装饰器。有关详细信息,请参阅 使用装饰器

修改类属性,将其转换为 getter,该 getter 从渲染根返回一个节点。当为 true 时,可选的第二个参数只执行一次 DOM 查询并缓存结果。这可以用作性能优化,在这种情况下,查询的节点不会更改。

此装饰器等同于

query 相同,只是它返回所有匹配的节点,而不是单个节点。它等同于调用 querySelectorAll

在这里,_divs 将返回模板中的两个 <div> 元素。对于 TypeScript,@queryAll 属性的类型为 NodeListOf<HTMLElement>。如果您确切地知道要检索的节点类型,则类型可以更具体

buttons 后的感叹号 (!) 是 TypeScript 的 非空断言运算符。它告诉编译器将 buttons 始终视为已定义,永不为 nullundefined

类似于 @query,只是它不是直接返回节点,而是返回一个 Promise,该 Promise 在任何挂起的元素渲染完成后解析为该节点。代码可以使用它,而不是等待 updateComplete 承诺。

例如,如果 @queryAsync 返回的节点可能会由于另一个属性更改而更改,这将很有用。

您的组件可能会接受子元素(例如 <ul> 元素可以具有 <li> 子元素)。

默认情况下,如果元素有 Shadow 树,则其子元素根本不会渲染。

要渲染子元素,您的模板需要包含一个或多个 <slot> 元素,这些元素充当子节点的占位符。

要渲染元素的子元素,请在元素的模板中为它们创建一个 <slot>。子元素不会在 DOM 树中移动,但它们会像如果它们是 <slot> 的子元素一样被渲染。例如

要将子元素分配给特定插槽,请确保子元素的 slot 属性与插槽的 name 属性匹配

  • 命名插槽只接受具有匹配 slot 属性的子元素。

    例如,<slot name="one"></slot> 仅接受具有 slot="one" 属性的子元素。

  • 具有 slot 属性的子元素将仅渲染在具有匹配 name 属性的插槽中。

    例如,<p slot="one">...</p> 将仅放置在 <slot name="one"></slot> 中。

您可以为插槽指定回退内容。当没有子元素分配给插槽时,将显示回退内容。

渲染回退内容。如果任何子节点被分配给插槽,则其回退内容不会渲染。没有名称的默认插槽接受任何子节点。即使唯一的分配节点是包含空格的文本节点,它也不会渲染回退内容,例如 <example-element> </example-element>。在使用 Lit 表达式作为自定义元素的子元素时,请确保在适当的情况下使用非渲染值,以便渲染任何插槽回退内容。有关更多信息,请参阅 删除子元素内容

要访问分配给 Shadow 根中插槽的子元素,您可以使用标准 slot.assignedNodesslot.assignedElements 方法以及 slotchange 事件。

例如,您可以创建一个 getter 来访问分配给特定插槽的元素

您还可以使用 slotchange 事件,当分配的节点更改时采取行动。以下示例提取所有插槽子元素的文本内容。

有关更多信息,请参阅 MDN 上的 HTMLSlotElement

@queryAssignedElements 和 @queryAssignedNodes 装饰器

@queryAssignedElements 和 @queryAssignedNodes 装饰器的永久链接

@queryAssignedElements@queryAssignedNodes 将类属性转换为 getter,该 getter 返回分别在组件 Shadow 树中的给定插槽上调用 slot.assignedElementsslot.assignedNodes 的结果。使用它们来查询分配给给定插槽的元素或节点。

两者都接受一个可选对象,该对象具有以下属性

属性描述
flatten布尔值,指定是否通过用其分配的节点替换任何子 <slot> 元素来扁平化分配的节点。
slot插槽名称,指定要查询的插槽。保留未定义以选择默认插槽。
selector (仅 queryAssignedElements)如果指定,则仅返回与该 CSS 选择器匹配的分配元素。

决定使用哪个装饰器取决于您是想查询分配给插槽的文本节点,还是仅查询元素节点。此决定特定于您的用例。

使用装饰器。装饰器是提议的 JavaScript 功能,因此您需要使用像 Babel 或 TypeScript 这样的编译器来使用装饰器。有关详细信息,请参阅 使用装饰器

上面的示例等同于以下代码

每个 Lit 组件都有一个渲染根——一个 DOM 节点,充当其内部 DOM 的容器。

默认情况下,LitElement 创建一个开放的 shadowRoot 并在其中进行渲染,从而生成以下 DOM 结构

有两种方法可以自定义 LitElement 使用的渲染根

  • 设置 shadowRootOptions
  • 实现 createRenderRoot 方法。

自定义渲染根的最简单方法是设置 shadowRootOptions 静态属性。createRenderRoot 的默认实现将 shadowRootOptions 作为选项参数传递给 attachShadow,用于创建组件的 Shadow 根。它可以设置为自定义 ShadowRootInit 字典中允许的任何选项,例如 modedelegatesFocus

有关更多信息,请参阅 MDN 上的 Element.attachShadow()

createRenderRoot 的默认实现创建了一个开放的阴影根,并向其中添加了 static styles 类字段中设置的任何样式。有关样式的更多信息,请参阅 样式

要自定义组件的渲染根,请实现 createRenderRoot 并返回您希望模板渲染到的节点。

例如,要将模板渲染到主 DOM 树中作为元素的子元素,请实现 createRenderRoot 并返回 this

渲染到子元素中。 通常不建议渲染到子元素而不是阴影 DOM。您的元素将无法访问 DOM 或样式范围,也无法将元素组合到其内部 DOM 中。