你正在查看 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-html 和 lit-element 整合到一个易用的包中。使用以下命令进行升级:

npm uninstall lit-element lit-html
npm install lit

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

从:

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

到:

import {LitElement, html} from 'lit';

虽然 lit-element@^3 和 lit-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.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 在 Lit 2.0 中已重命名。如果使用了这些 API,在代码库中直接重命名应该是安全的:

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

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

概念旧 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)

下面是一个 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 已经存在。此更改是为了兼容服务器端渲染。
  • ReactiveElement 的 initialize 方法已被移除。这项工作现在在元素构造函数中完成。
  • LitElement 基类上的 静态 render 方法已被移除。这主要用于实现 ShadyDOM 集成,并不是作为用户可重写的方法。ShadyDOM 集成现在通过 polyfill-support 模块实现。
  • 当属性声明为 reflect: true 且其 toAttribute 函数返回 undefined 时,属性现在会被移除,而之前是保持不变(#872)。
  • attributeChangedCallback 中的脏检查已被移除。虽然在技术上是破坏性的,但实际上影响应该非常少(#699)。
  • LitElement 的 adoptStyles 方法已被移除。样式现在在 createRenderRoot 中被采用。可以重写此方法来自定义此行为。
  • 移除了 requestUpdateInternal。requestUpdate 方法现在与该方法相同,应使用前者替代。
  • 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')。