Lit 速查表

— 更新于
Photo of Elliott Marquez
Elliott Marquez

你需要一份 Lit 基础知识的快速参考吗?别再找了!这份速查表将帮助你入门或回忆 Lit 的各项功能。

如果你是从其他框架转来的,你可能还想配合 Component Party 一起阅读这篇文章,它比较了不同框架的基本概念。只需确保在页面顶部选择了 Lit 即可!

使用目录跳转到特定章节!

LitElement 是所有 Lit 组件的基类。

@customElementcustomElements.define是你将组件名称与类定义/逻辑关联起来的地方。

render() 是你使用带标签的模板字面量和 html 定义组件模板的方法。

html 标签模板字面量中编写你的 HTML。

重要规则:

组件是全局 HTML 元素,目前在同一页面上不能有多个同名组件。

组件名称中必须包含连字符(使用 @customElementcustomElements.define 定义)。

相关文档和主题:

要使用一个组件,请导入其定义所在的文件。

相关文档和主题:

使用 html 标签模板字面量来定义你的组件模板。

相关文档和主题:

在模板中使用标准的 JavaScript 条件表达式来有条件地渲染内容。

相关文档和主题:

属性和属性表达式(绑定语法)

Permalink to "属性和属性表达式(绑定语法)"

lit-html 有三种内置表达式来设置元素的属性或特性:

  • 属性表达式 .prop=${value}
  • 特性表达式 attr=${value}
  • 布尔特性表达式 ?attr=${value}

相关文档和主题:

lit-html 有一个内置的事件监听器表达式,用于为元素添加事件监听器。你还可以使用事件监听器来模拟与输入元素的双向数据绑定。

相关文档和主题:

lit-html 可以渲染 JavaScript 数组和可迭代对象。对于大多数简单场景,你可以使用 Array.map() 方法来渲染项目数组,或使用 map() 指令来渲染其他可迭代对象。这种模式最适合简短、简单的列表。

相关文档和主题:

对于可能频繁变化的长列表,使用 repeat() 指令来高效地仅重新渲染已更改的项目。

对于过长而无法一次性渲染所有项目的列表,使用 Lit Virtualizer 来仅渲染当前可见的项目。

Lit Virtualizer 处于实验阶段

这意味着它的实现可能会在毕业并变得稳定之前发生变化。此外,virtualizer 还有更多功能,建议查阅文档。

相关文档和主题:

要将 HTML 字符串作为 HTML 渲染到 Lit 中,请使用 unsafeHTML 指令。

使用 unsafeHTML 时要小心,因为它可能使你的应用程序面临跨站脚本攻击(XSS)和其他攻击。

仅在受信任的来源和字符串中使用 unsafeHTML,就像你使用 Element.prototype.innerHTML 一样。

相关文档和主题:

在极少数情况下,你需要绑定到 HTML 标签名来更改渲染的元素。你可以使用 static-html 模块和 literal 模板标签来安全地实现这一点。

使用静态 HTML 模板切换标签名时要小心,因为每次标签名改变时都需要重新应用模板中的所有绑定。

这可能代价较高,在大多数情况下,建议使用条件模板渲染而不是通过静态 HTML 模板切换标签名。

相关文档和主题:

将任意值绑定到 HTML 标签名或属性名

Permalink to "将任意值绑定到 HTML 标签名或属性名"

在更罕见的情况下,你需要将任意字符串值绑定到 HTML 标签名以更改渲染的元素。你可以使用 unsafeStatic() 指令来实现这一点。如果你正在实现一个使用 lit-html 进行渲染的 SSR 框架,这可能会很有帮助。

使用 unsafeStatic 时要小心,因为它可能使你的应用程序面临跨站脚本攻击(XSS)和其他攻击。

仅在受信任的来源和字符串中使用 unsafeStatic,就像你使用 Element.prototype.innerHTML 一样。此外,unsafeStatic 不会被缓存,每次值改变时都会重新渲染整个模板,这可能会对性能产生负面影响。

通过定义 static styles 属性来添加样式。在 css 标签模板字面量中编写 CSS。

相关文档和主题:

样式应用于当前元素。这意味着你可以放心使用那些通常需要编造类名的超级通用选择器。

相关文档和主题:

要有条件地应用样式,通常最好使用 classMap

相关文档和主题:

你可以通过从模块导出样式表并将其导入到另一个组件中来与其他组件共享 Lit 样式表。

通过 CSS 自定义属性继承 Shadow DOM 样式

Permalink to "通过 CSS 自定义属性继承 Shadow DOM 样式"

CSS 自定义属性可以穿透多个 shadow root,允许你为特定属性共享值。

相关文档和主题:

使用 CSS Shadow Parts 设置任意样式

Permalink to "使用 CSS Shadow Parts 设置任意样式"

CSS Shadow Parts 通过组件的 part="<part-name>" 属性暴露。

Shadow Parts 可以穿透单个 shadow root,允许你使用 ::part(<part-name>) 伪元素对给定节点设置任意样式。

相关文档和主题:

CSS Shadow part 名称只能应用于目标元素。你需要使用 exportparts 来在嵌套的 shadow root 中暴露 shadow part。

你可以用逗号(,)分隔来导出多个 part。

你还可以用冒号(:)来重命名 part。

相关文档和主题:

在某些罕见情况下,你可能会收到受信任的样式字符串,并需要将其应用于组件。你可以使用原生的可构建样式表来实现。

使用可构建样式表时要小心,因为它可能使你的应用程序面临隐私和安全漏洞。

仅在受信任的来源和字符串中使用可构建样式表。

相关文档和主题:

在某些情况下,你可能希望以 CSS 文件的形式导入样式,而不是 Lit 的 CSSResult 或字符串。目前

这是最近添加到某些浏览器的新功能。

请在 MDN 上查看浏览器兼容性

由于此功能较新,以下示例使用 JavaScript,可能无法在某些浏览器中运行。

一些对此方法支持更好的替代方案可能包括:

  • 一个构建工具插件,将你的 CSS 导入转换为 Lit 的 CSSResult,类似于 rollup-plugin-lit-css
  • 使用打包工具将你的 CSS 导入转换为字符串,然后使用可构建样式表
  • 在模板中使用 <link rel="stylesheet" href="...">,但这会导致 FOUC(无样式内容闪烁)

相关文档和主题:

  • 将样式隔离到 shadow root 中
  • 将 DOM 隔离到 shadow root 中
    • 无法从 shadow root 外部通过 querySelector 调用定位
  • 通过 <slot> 元素实现内容插槽
  • 通过 CSS 自定义属性和 CSS Shadow Parts 暴露 CSS 的 API

相关文档和主题:

你可以通过重写 createRenderRoot() 方法并将渲染根节点设置为元素本身来关闭 Shadow DOM。

通常不推荐这样做,但对于需要与较旧的系统或尚未更新以支持 Shadow DOM 的库进行集成时,这有时可能是值得的。

由于 Shadow root 不再存在,<slot> 将不起作用,Lit 也不会再为你处理 static styles 属性。你必须自己决定如何处理样式。

相关文档和主题:

将组件插槽到另一个组件的 Shadow DOM 中

Permalink to "将组件插槽到另一个组件的 Shadow DOM 中"

你可以使用 <slot> 元素将组件插槽到另一个组件的 Shadow DOM 中。如果你熟悉 React,这类似于 props.children

相关文档和主题:

插槽组件使用浏览器原生的 Shadow DOM 投影功能。为了保持强大、高性能和封装的样式,浏览器厂商对插槽内容的样式设置施加了限制。

你可以使用 ::slotted() 伪选择器为直接插槽的元素添加样式。如果你想为插槽内容的子元素添加样式,应该使用 CSS 自定义属性。

相关文档和主题:

开启 delegatesFocus 和其他 shadow root 选项

Permalink to "开启 delegatesFocus 和其他 shadow root 选项"

你可以通过重写静态 shadowRootOptions 成员来设置传递给 Element.attachShadow() 的 shadow root 选项。

相关文档和主题:

响应式属性是组件内部的属性,当它们改变时会自动触发重新渲染。这些属性可以从组件外部设置。

它们还通过接受属性并将其转换为对应的属性来处理特性。

你可以使用@property 装饰器static properties = { propertyName: {...}} 代码块并在 constructor() 中初始化它们来定义响应式属性。

相关文档和主题:

响应式状态是组件私有的属性,不对外暴露。这些属性用于存储组件的内部状态,当它们改变时应触发 Lit 生命周期的重新渲染。

你可以使用@state 装饰器static properties = { propertyName: {state: true, ...}} 代码块并在属性信息中设置 state: true 标志。你可以在 constructor() 中初始化它们来定义响应式属性。

相关文档和主题:

重新渲染数组或对象的更改

Permalink to "重新渲染数组或对象的更改"

数组在 JavaScript 中是对象,Lit 的默认变更检测使用严格相等来判断数组是否改变。当数组通过 .push().pop() 等方式被修改时,如果需要重新渲染组件,你需要让 Lit 知道数组已经改变了。

最常用的方法是:

  • 使用 requestUpdate() 方法手动触发重新渲染
  • 创建新的数组/对象引用

响应式属性定义中的自定义 hasChanged() 方法在这里帮不上太多忙。

hasChanged() 函数仅在属性被设置时调用,而不是在属性被修改时。这仅在数组或对象被赋予新的引用且你_不想_触发重新渲染时才会有帮助。

如果这是你的使用场景,你通常最好使用 repeat() 指令

相关文档和主题:

在高级场景中,你可能需要以特殊方式将属性值转换为属性,反之亦然。你可以使用自定义属性转换器来实现这一点。

属性转换器仅在元素上设置了属性或响应式属性设置了 reflect: true 选项时运行。

相关文档和主题:

有时创建一个从其他属性或状态派生的属性会很有帮助。最简单的方法是使用原生的 getter。

相关文档和主题:

如果你有多个相互依赖的响应式属性,你可以在 Lit 的 willUpdate() 生命周期方法中协调它们的值。

willUpdate() 是协调属性之间值的好地方,因为它也可以在服务器上运行,因为 willUpdate() 在 Lit SSR 的服务端渲染期间会被调用。

将响应式属性与浏览器功能同步

Permalink to "将响应式属性与浏览器功能同步"

如果你有依赖浏览器 API(例如 localStorage)的响应式属性,你可以在 Lit 的 update() 生命周期方法中协调它们的值。

update() 是协调需要访问浏览器 API 或 DOM 的属性之间值的好地方。update() 发生在渲染之前。

相关文档和主题:

在响应式属性和 DOM 之间协调值

Permalink to "在响应式属性和 DOM 之间协调值"

如果你有多个依赖组件已渲染 DOM 的计算结果的响应式属性,你可以在 Lit 的 updated() 生命周期方法中协调它们的值。

updated() 是协调需要访问已渲染 DOM 的属性之间值的好地方,因为 updated() 在组件渲染模板之后被调用。但强烈建议除非必要,否则不要在 updated() 中更新响应式属性,因为它可能在刚完成渲染后触发重新渲染。Lit 很快,但这仍然是不必要的工作。

相关文档和主题:

Lit 中有两种生命周期:原生 Web 组件生命周期和 Lit 在其之上添加的用于帮助处理属性和状态变化的生命周期。

还有更多的生命周期事件可以在文档中找到,但你通常会使用的是以下这些,它们的大致顺序如下:

  1. constructor – (原生自定义元素生命周期)
  2. connectedCallback – (原生)
  3. willUpdate – (Lit 生命周期)
  4. update – (Lit)
  5. render – (Lit)
  6. firstUpdated – (Lit)
  7. updated – (Lit)
  8. disconnectedCallback – (原生)

Lit 生命周期和原生自定义元素生命周期是不同的,分别管理。

这意味着虽然它们通常遵循特定的顺序,但它们可能会交错运行,因为浏览器控制原生生命周期,而 Lit 和 JavaScript 管理 Lit 生命周期。

例如,一个组件可能被附加到 DOM 然后在 Lit 生命周期运行之前就被移除了,或者一个组件可能通过 document.createElement 创建(这会调用 constructor),但如果它从未被添加到 DOM,connectedCallback 将永远不会运行,因此 Lit 生命周期也永远不会运行。

相关文档和主题:

  • 当元素通过以下方式创建时运行:
    • document.createElement('my-element')
    • element.innerHTML = '<my-element></my-element>'
    • new MyElement()
  • 是原生浏览器回调
  • 需要调用 super()
  • 设置初始属性的好地方
  • 不要在构造函数中添加参数或修改 DOM
  • 可能在服务器上运行。(这一点尚未确定。)
  • 当元素通过以下方式添加到 DOM 时运行:
    • element.appendChild(element)
    • element.innerHTML = '<my-element></my-element>'
  • 是原生浏览器回调
  • 需要调用 super.connectedCallback()
  • 可以运行多次,但它是设置对外部元素(如 document)的事件监听器的好地方
  • 在服务器上运行 – 不要访问 DOM 或浏览器 API
  • 不是原生浏览器回调(Lit 特有的方法)
  • 不需要调用 super.willUpdate()
  • 设置依赖其他属性的属性的好地方
  • willUpdate 之后运行
  • 不是原生浏览器回调(Lit 特有的方法)
  • 通常需要在自定义逻辑之后调用 super.update()
  • 更新依赖其他属性(这些属性又依赖 DOM)的属性的好地方
  • 不是原生浏览器回调(Lit 特有的方法)
  • 不需要调用 super.render()
  • 在服务器上运行 - 不要访问 DOM 或浏览器 API
  • 在首次渲染之后运行
  • 不是原生浏览器回调(Lit 特有的方法)
  • 不需要调用 super.firstUpdated()
  • 执行需要访问组件已渲染 DOM 的初始化操作的好地方
  • renderfirstUpdated 之后运行
  • 不是原生浏览器回调(Lit 特有的方法)
  • 不需要调用 super.updated()
  • 执行需要组件已渲染 DOM 的更新操作或更新依赖已渲染 DOM 的属性的好地方
  • 避免在此生命周期中设置响应式属性,因为这样做可能触发不必要的重新渲染。如果可能的话,尽量在 willUpdateupdate 中完成。
  • 在元素从 DOM 中移除之后调用
  • 是原生浏览器回调
  • 需要调用 super.connectedCallback()
  • 清理事件监听器的好地方

所有 Lit 元素都有异步生命周期。这样做的原因是属性更改(例如 el.foo = 1; el.bar = 2;)会被批量处理以提高效率和正确性。

你需要等待 updateComplete promise 解析后才能确定元素已完成 DOM 更新。

相关文档和主题:

如果你需要在 Lit Element 中执行异步任务,你可能想使用 @lit/task 包。它处理了管理异步任务和 Lit 生命周期结合的基础工作。

以下示例根据宝可梦名称从 PokeAPI 按 ID 获取宝可梦信息。为此你需要:

  1. 使用 new Task(...) 初始化任务
  2. Task 是一个响应式控制器,所以你需要传递一个对响应式元素(this)的引用
  3. 编写一个异步函数来获取并返回数据
  4. 给 Task 一个函数,该函数返回 Task 所依赖的响应式属性
  5. render() 方法中使用 Task.prototype.render() 渲染 Task 的所有状态

相关文档和主题:

一个常见模式是在 constructor() 中向宿主元素添加事件监听器。不需要手动移除这些监听器,因为当元素不再被引用时,浏览器的垃圾回收器会自动清理它们。

相关文档和主题:

一个常见模式是在 connectedCallback 中向全局节点(如 documentwindow)添加事件监听器,并在 disconnectedCallback 中移除它们。

相关文档和主题:

向下传递数据最简单的方式是使用属性和特性。

例如,你可以使用属性绑定向下传递数据到子组件:

.name=${'Steven'}

对于布尔特性,使用问号代替点号:

?programmer=${true}

你通常希望使用@property() 而不是 @state()static properties = {propName: {state: false}}来暴露组件的外部特性和属性 API。

相关文档和主题:

要向上传递数据到祖先元素,你可以分发自定义事件。要发出一个事件,使用 Element.dispatchEvent()

dispatchEvent() 接受一个事件对象作为第一个参数。这样构建一个自定义事件对象:

new CustomEvent('event-name', {detail: data, bubbles: true, composed: true})

在事件的 detail 属性中提供你想传递给祖先的数据,祖先可以通过为组件添加事件监听器来响应事件:

@event-name=${this.eventHandler}

如果你想让事件冒泡穿过 Shadow Root,设置 composed: true

相关文档和主题:

如果你需要将数据向下传递到子树而不使用属性或"属性逐层传递",你可能想使用 @lit/context

相关文档和主题:

@query 装饰器允许你使用 ShadowRoot.prototype.querySelector() 的语法来访问组件 Shadow DOM 中单个元素的引用。

在 JavaScript 中,你可以使用 this.shadowRoot.querySelector() 来访问元素。

注意:DOM 通常在 firstUpdated 被调用后才准备就绪。

这意味着 DOM 在首次渲染时也可以通过 updated() 访问,但在后续渲染之前无法在 constructor()connectedCallback()willUpdate() 中访问。

ref() 指令是 lit-html 特有的获取元素引用的方法。ref() 指令在以下情况下是很好的替代方案:

  • 你不能使用 @query 装饰器(或其 JS 等效写法)
  • 你无法确定元素何时会被渲染
  • 你需要将元素引用从子组件传递到父组件(不常见)
  • 你正在从 React 等其他框架迁移
  • 你需要在引用的元素变化时运行一个函数

ref() 指令还接受一个回调函数,当目标元素连接到 DOM 时,会以元素引用作为参数调用该函数。

不过,通常建议尽可能使用 @query@queryAsync 装饰器,因为它们通常性能更好且对 Lit 的依赖更少。

相关文档和主题:

@queryAsync 装饰器与 @query 装饰器类似,但它会等待当前宿主元素完成更新后再解析。当你需要访问由组件状态变化异步渲染的元素时,这很有用。

JavaScript 等效写法是在调用 this.shadowRoot.querySelector() 之前等待 this.updateComplete promise。

相关文档和主题:

Shadow DOM 使用 <slot> 元素,它允许你将 shadow root 外部的内容投影到 shadow root 中。你可以使用@queryAssignedElements 装饰器HTMLSlotElement.assignedElements() 方法来访问插槽内容。

你需要给它一个要访问的插槽名称和要过滤的元素选择器。

相关文档和主题:

Signals 是用于管理可观察状态的数据结构。它们要么存储一个值,要么根据其他信号计算一个值。Lit 项目尝试通过 @lit-labs/signals 遵循 Signals 标准提案,以提供跨框架的响应式状态管理解决方案标准。

常见 Signal 设置(SignalWatcher)

Permalink to "常见 Signal 设置(SignalWatcher)"

在 Lit 中使用 signals 最常见的方式是使用 SignalWatcher mixin。当被访问的 signal 值改变时,SignalWatcher 会触发 Lit 元素的更新生命周期。这包括在 shouldUpdate()willUpdate()update()render()updated()firstUpdated() 以及响应式控制器的 hostUpdate()hostUpdated() 中读取的 signals。

相关文档和主题:

精确 Signal 更新(watch 指令)

Permalink to "精确 Signal 更新(watch 指令)"

watch() 指令允许你精确指定 signal 应该在哪里更新 DOM,而无需重新触发 lit-html 的重新渲染。这意味着使用 watch() 指令不会触发 render(),除非它触发了传统 Lit 响应式属性的更改。

这可能是优化 Lit 组件性能的一种有帮助的方式,但请始终针对你的使用场景进行测量

相关文档和主题:

@lit-labs/signals 包还提供了一个 html 模板标签,可以替代 Lit 默认的 html 标签使用,它会自动将模板中的任何 signals 用 watch() 指令包装。

相关文档和主题:

从其他 Signals 创建 Signal 值(computed)

Permalink to "从其他 Signals 创建 Signal 值(computed)"

有时你需要从其他 signals 派生一个值。你可以使用 computed() signal 来实现这一点。

相关文档和主题:

官方的 signal-utils 包目前提供了一个实验性的 effect() 函数,允许你响应 signal 变化并运行副作用。

signal-utils 包中的 effect() 函数是实验性的。

请关注 signal-utils 获取此项目的更新。

相关文档和主题:

在组件间共享全局响应式数据

Permalink to "在组件间共享全局响应式数据"

如果你的组件需要与另一个组件共享全局状态,且你不需要你的组件兼容 Lit 的声明式事件监听器语法,你可以使用共享 signal 来在组件间共享状态。

这里是向上分发事件一节中的计分板示例,但使用了共享 signals。

相关文档和主题: