你正在查看 Lit 的旧版本文档。点击 这里查看最新版本。

升级指南

Lit 2.0 设计为与为 LitElement 2.x 和 lit-html 1.x 编写的大多数代码兼容。将你的代码迁移到 Lit 2.0 需要少量的更改。高层级的更改包括:

  1. 更新 npm 包和导入路径。
  2. 在加载 Web Components polyfill 时加载 polyfill-support 脚本。
  3. 更新所有自定义指令实现以使用新的基于类的 API 和相关辅助工具。
  4. 更新已重命名的 API 代码。
  5. 适配一些小的破坏性变更,主要在不常见的场景中。

以下各节将详细介绍每个更改。

Lit 2.0 提供了一个一站式的 lit 包,将 lit-htmllit-element 整合到一个易用的包中。使用以下命令进行升级:

npm uninstall lit-element lit-html
npm install lit

并相应地重写你的模块导入:

从:

import {LitElement, html} from 'lit-element';

到:

import {LitElement, html} from 'lit';

虽然 lit-element@^3lit-html@^2 包在很大程度上应该是向后兼容的,但我们建议更新到 lit 包,因为其他包将逐步弃用。

之前的 lit-element 版本从主模块导出了所有 TypeScript 装饰器。在 Lit 2.0 中,这些装饰器已移到单独的模块中,以便在未使用装饰器时实现更小的打包体积。

从:

import {property, customElement} from 'lit-element';

到:

import {property, customElement} from 'lit/decorators.js';

内置的 lit-html 指令现在也从 lit 包中导出。

从:

import {repeat} from 'lit-html/directives/repeat.js';

到:

import {repeat} from 'lit/directives/repeat.js';

如果单独使用 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.jsweb-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 在 Lit 2.0 中已重命名。如果使用了这些 API,在代码库中直接重命名应该是安全的:

旧名称新名称说明
UpdatingElementReactiveElementLitElement 的基类。命名现在与我们用于描述其响应式生命周期的术语保持一致。
@internalProperty@state用于 LitElement / ReactiveElement 的装饰器,用于标记触发更新的私有状态,与用户可设置的、使用 @property 装饰器的元素公共属性相对。
static getStyles()static finalizeStyles(styles)LitElementReactiveElement 类上用于重写样式处理的方法。注意它现在还接受一个参数,反映该类的静态样式。
_getUpdateComplete()getUpdateComplete()LitElementReactiveElement 类上用于重写 updateComplete Promise 的方法
NodePartChildPart通常仅在指令代码中使用;见下文。

虽然 使用 指令的 API 应该与 1.x 100% 向后兼容,但自定义指令的_编写方式_有一个破坏性变更。该 API 变更改善了编写有状态指令的人体工程学,同时为 SSR 兼容的指令提供了清晰的模式:在服务器端只会调用 render,而不会调用 update

概念旧 API新 API
代码惯用方式接收指令参数的函数,返回接收 part 并返回值的函数继承 Directive 的类,具有接收指令参数的 updaterender 方法
声明式渲染将值传递给 part.setValue()render() 方法返回值
DOM 操作在指令函数中实现update() 方法中实现
状态存储在以 part 为键的 WeakMap存储在类实例字段中
Part 验证每次渲染时使用 instanceof 检查 part在构造函数中使用 part.type 检查
异步更新part.setValue(v);
part.commit();
继承 AsyncDirective 而非 Directive 并调用 this.setValue(v)

下面是一个 lit-html 1.x 指令的示例,以及如何将其迁移到新 API:

1.x 指令 API:

import {html, directive, Part, NodePart} from 'lit-html';

// 状态存储在 WeakMap 中
const previousState: WeakMap<Part, number> = new WeakMap();

// 基于函数的指令 API
export 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';

// 基于类的指令 API
export 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);

为完整起见,以下是一些小的但值得注意的破坏性变更,你可能需要适配你的代码。我们预计这些变更影响的用户相对较少。

  • 为简化起见,requestUpdate 不再返回 Promise。请改为等待 updateComplete Promise。
  • 更新周期中发生的错误之前会被抑制,以允许后续更新正常进行。现在错误会被异步重新触发,以便可以被检测到。可以通过 window 上的 unhandledrejection 事件处理器来观察错误。
  • 通过 createRenderRoot 创建 shadowRoot 以及将 static styles 应用到 shadowRoot 的支持已从 LitElement 移至 ReactiveElement
  • createRenderRoot 方法现在在第一次更新之前调用,而不是在构造函数中。元素代码不能假设在元素 hasUpdated 之前 renderRoot 已经存在。此更改是为了兼容服务器端渲染。
  • ReactiveElementinitialize 方法已被移除。这项工作现在在元素构造函数中完成。
  • LitElement 基类上的 静态 render 方法已被移除。这主要用于实现 ShadyDOM 集成,并不是作为用户可重写的方法。ShadyDOM 集成现在通过 polyfill-support 模块实现。
  • 当属性声明为 reflect: true 且其 toAttribute 函数返回 undefined 时,属性现在会被移除,而之前是保持不变(#872)。
  • attributeChangedCallback 中的脏检查已被移除。虽然在技术上是破坏性的,但实际上影响应该非常少(#699)。
  • LitElement 的 adoptStyles 方法已被移除。样式现在在 createRenderRoot 中被采用。可以重写此方法来自定义此行为。
  • 移除了 requestUpdateInternalrequestUpdate 方法现在与该方法相同,应使用前者替代。
  • render() 在首次渲染时不再清除其渲染到的容器。它现在默认追加到容器中。
  • 注释中的表达式不会被渲染或更新。
  • 模板缓存按每个调用点进行,而不是按模板标签/调用点对。这意味着一些罕见的高度动态的模板标签形式不再被支持。
  • 传递给属性绑定的数组和其他可迭代对象不再被特殊处理。数组将使用其默认的 toString 表示来渲染。这意味着 html`<div class=${['a', 'b']}> 将渲染为 <div class="a,b"> 而不是 <div class="a b">。要获得旧行为,请使用 array.join(' ')
  • RenderOptionstemplateFactory 选项已被移除。
  • TemplateProcessor 已被移除。
  • Symbols 在修改 DOM 之前不再被转换为字符串,因此将 Symbol 传递给属性或文本绑定将导致异常。
  • 属性表达式中的 ifDefined 指令现在对 nullundefined 都会移除属性,而不仅仅是 undefined
  • 将值 nothing 渲染到属性表达式会导致属性被移除——即使属性值位置有多个表达式而只有一个为 nothing。例如,给定 src="${baseurl}/${filename}",如果 baseurlfilename 中_任意一个_计算为 nothing,则 src 属性会被移除。
  • unsafeHTMLunsafeSVG 指令中渲染值 nothingnullundefined 现在将导致不渲染任何内容(之前会分别渲染 '[object Object]''null''undefined')。