升级指南
Lit 2.0 设计为与为 LitElement 2.x 和 lit-html 1.x 编写的大多数代码兼容。将你的代码迁移到 Lit 2.0 需要少量的更改。高层级的更改包括:
- 更新 npm 包和导入路径。
- 在加载 Web Components polyfill 时加载
polyfill-support脚本。 - 更新所有自定义指令实现以使用新的基于类的 API 和相关辅助工具。
- 更新已重命名的 API 代码。
- 适配一些小的破坏性变更,主要在不常见的场景中。
以下各节将详细介绍每个更改。
更新包和导入路径
Permalink to "更新包和导入路径"使用 lit 包
Permalink to "使用 lit 包" Lit 2.0 提供了一个一站式的 lit 包,将 lit-html 和 lit-element 整合到一个易用的包中。使用以下命令进行升级:
npm uninstall lit-element lit-htmlnpm install lit 并相应地重写你的模块导入:
从:
import {LitElement, html} from 'lit-element'; 到:
import {LitElement, html} from 'lit'; 虽然 lit-element@^3 和 lit-html@^2 包在很大程度上应该是向后兼容的,但我们建议更新到 lit 包,因为其他包将逐步弃用。
更新装饰器导入
Permalink to "更新装饰器导入"之前的 lit-element 版本从主模块导出了所有 TypeScript 装饰器。在 Lit 2.0 中,这些装饰器已移到单独的模块中,以便在未使用装饰器时实现更小的打包体积。
从:
import {property, customElement} from 'lit-element'; 到:
import {property, customElement} from 'lit/decorators.js'; 更新指令导入
Permalink to "更新指令导入"内置的 lit-html 指令现在也从 lit 包中导出。
从:
import {repeat} from 'lit-html/directives/repeat.js'; 到:
import {repeat} from 'lit/directives/repeat.js'; 更新独立的 lit-html 导入
Permalink to "更新独立的 lit-html 导入"如果单独使用 lit-html(在 LitElement 之外),你可以从 lit/html.js 入口点导入特定于独立使用的 API(如 render):
从:
import {render, html} from 'lit-html'; 到:
import {render, html} from 'lit/html.js'; 使用 Web Components polyfill 时加载 polyfill-support
Permalink to "使用 Web Components polyfill 时加载 polyfill-support" Lit 2.0 仍然支持相同的浏览器,最低到 IE11。然而,鉴于现代浏览器广泛采用了 Web Components API,我们借此机会将所有与 Web Components polyfill 交互所需的代码从核心库中移出,放到一个可选的支持文件中,这样只有在需要时才会承担支持旧浏览器的开销。
通常,任何时候你使用 Web Components polyfill 时,也应该在页面上加载一次 lit/polyfill-support.js 支持文件,类似于 polyfill。例如:
<script src="node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"><script src="node_modules/lit/polyfill-support.js"> 如果使用 @web/test-runner 或 @web/dev-server 配合 legacyPlugin 进行开发,在你的 web-test-runner.config.js 或 web-dev-server.config.js 文件中添加以下配置,将使其在需要时自动注入支持文件:
export default { ... plugins: [ legacyPlugin({ polyfills: { webcomponents: true, custom: [ { name: 'lit-polyfill-support', path: 'node_modules/lit/polyfill-support.js', test: "!('attachShadow' in Element.prototype)", module: false, }, ], }, }), ],};
更新已重命名的 API
Permalink to "更新已重命名的 API"以下高级 API 在 Lit 2.0 中已重命名。如果使用了这些 API,在代码库中直接重命名应该是安全的:
| 旧名称 | 新名称 | 说明 |
|---|---|---|
UpdatingElement | ReactiveElement | LitElement 的基类。命名现在与我们用于描述其响应式生命周期的术语保持一致。 |
@internalProperty | @state | 用于 LitElement / ReactiveElement 的装饰器,用于标记触发更新的私有状态,与用户可设置的、使用 @property 装饰器的元素公共属性相对。 |
static getStyles() | static finalizeStyles(styles) | LitElement 和 ReactiveElement 类上用于重写样式处理的方法。注意它现在还接受一个参数,反映该类的静态样式。 |
_getUpdateComplete() | getUpdateComplete() | LitElement 和 ReactiveElement 类上用于重写 updateComplete Promise 的方法 |
NodePart | ChildPart | 通常仅在指令代码中使用;见下文。 |
更新自定义指令实现
Permalink to "更新自定义指令实现"虽然 使用 指令的 API 应该与 1.x 100% 向后兼容,但自定义指令的_编写方式_有一个破坏性变更。该 API 变更改善了编写有状态指令的人体工程学,同时为 SSR 兼容的指令提供了清晰的模式:在服务器端只会调用 render,而不会调用 update。
指令 API 变更概览
Permalink to "指令 API 变更概览"| 概念 | 旧 API | 新 API |
|---|---|---|
| 代码惯用方式 | 接收指令参数的函数,返回接收 part 并返回值的函数 | 继承 Directive 的类,具有接收指令参数的 update 和 render 方法 |
| 声明式渲染 | 将值传递给 part.setValue() | 从 render() 方法返回值 |
| DOM 操作 | 在指令函数中实现 | 在 update() 方法中实现 |
| 状态 | 存储在以 part 为键的 WeakMap 中 | 存储在类实例字段中 |
| Part 验证 | 每次渲染时使用 instanceof 检查 part | 在构造函数中使用 part.type 检查 |
| 异步更新 | part.setValue(v);part.commit(); | 继承 AsyncDirective 而非 Directive 并调用 this.setValue(v) |
指令迁移示例
Permalink to "指令迁移示例"下面是一个 lit-html 1.x 指令的示例,以及如何将其迁移到新 API:
1.x 指令 API:
import {html, directive, Part, NodePart} from 'lit-html';
// 状态存储在 WeakMap 中const previousState: WeakMap<Part, number> = new WeakMap();
// 基于函数的指令 APIexport const renderCounter = directive((initialValue: number) => (part: Part) => { // 必要时,每次渲染使用 `instanceof` 验证 part 类型 if (!(part instanceof NodePart)) { throw new Error('renderCounter only supports NodePart'); } // 从之前的状态中获取值 let value = previousState.get(part); // 更新状态 if (value === undefined) { value = initialValue; } else { value++; } // 存储状态 previousState.set(part, value); // 使用新的渲染更新 part part.setValue(html`<p>${value}</p>`);}); 2.0 指令 API:
import {html} from 'lit';import {directive, Directive, Part, PartInfo, PartType} from 'lit/directive.js';
// 基于类的指令 APIexport class RenderCounter extends Directive { // 状态存储在类字段中 value: number | undefined; constructor(partInfo: PartInfo) { super(partInfo); // 必要时,在构造函数中使用 `part.type` 验证 part if (partInfo.type !== PartType.CHILD) { throw new Error('renderCounter only supports child expressions'); } } // 可选:重写 update 以执行任何直接的 DOM 操作 update(part: Part, [initialValue]: DirectiveParameters<this>) { /* 任何对 DOM/part 的命令式更新放在这里 */ return this.render(initialValue); } // 执行 SSR 兼容的渲染(参数从调用处传递) render(initialValue: number) { // 之前的状态在类字段上可用 if (this.value === undefined) { this.value = initialValue; } else { this.value++; } return html`<p>${this.value}</p>`; }}export const renderCounter = directive(RenderCounter); 适配小的破坏性变更
Permalink to "适配小的破坏性变更"为完整起见,以下是一些小的但值得注意的破坏性变更,你可能需要适配你的代码。我们预计这些变更影响的用户相对较少。
LitElement
Permalink to "LitElement" - 为简化起见,
requestUpdate不再返回 Promise。请改为等待updateCompletePromise。 - 更新周期中发生的错误之前会被抑制,以允许后续更新正常进行。现在错误会被异步重新触发,以便可以被检测到。可以通过 window 上的
unhandledrejection事件处理器来观察错误。 - 通过
createRenderRoot创建shadowRoot以及将static styles应用到shadowRoot的支持已从LitElement移至ReactiveElement。 createRenderRoot方法现在在第一次更新之前调用,而不是在构造函数中。元素代码不能假设在元素hasUpdated之前renderRoot已经存在。此更改是为了兼容服务器端渲染。ReactiveElement的initialize方法已被移除。这项工作现在在元素构造函数中完成。LitElement基类上的 静态render方法已被移除。这主要用于实现 ShadyDOM 集成,并不是作为用户可重写的方法。ShadyDOM 集成现在通过polyfill-support模块实现。- 当属性声明为
reflect: true且其toAttribute函数返回undefined时,属性现在会被移除,而之前是保持不变(#872)。 attributeChangedCallback中的脏检查已被移除。虽然在技术上是破坏性的,但实际上影响应该非常少(#699)。- LitElement 的
adoptStyles方法已被移除。样式现在在createRenderRoot中被采用。可以重写此方法来自定义此行为。 - 移除了
requestUpdateInternal。requestUpdate方法现在与该方法相同,应使用前者替代。
lit-html
Permalink to "lit-html" render()在首次渲染时不再清除其渲染到的容器。它现在默认追加到容器中。- 注释中的表达式不会被渲染或更新。
- 模板缓存按每个调用点进行,而不是按模板标签/调用点对。这意味着一些罕见的高度动态的模板标签形式不再被支持。
- 传递给属性绑定的数组和其他可迭代对象不再被特殊处理。数组将使用其默认的 toString 表示来渲染。这意味着
html`<div class=${['a', 'b']}>将渲染为<div class="a,b">而不是<div class="a b">。要获得旧行为,请使用array.join(' ')。 RenderOptions的templateFactory选项已被移除。TemplateProcessor已被移除。- Symbols 在修改 DOM 之前不再被转换为字符串,因此将 Symbol 传递给属性或文本绑定将导致异常。
- 属性表达式中的
ifDefined指令现在对null和undefined都会移除属性,而不仅仅是undefined。 - 将值
nothing渲染到属性表达式会导致属性被移除——即使属性值位置有多个表达式而只有一个为nothing。例如,给定src="${baseurl}/${filename}",如果baseurl或filename中_任意一个_计算为nothing,则src属性会被移除。 - 在
unsafeHTML或unsafeSVG指令中渲染值nothing、null或undefined现在将导致不渲染任何内容(之前会分别渲染'[object Object]'、'null'或'undefined')。