Lit 3 升级指南
如果你正在寻找从 Lit 1.x 迁移到 Lit 2.x 的指南,请参阅 Lit 2 升级指南。
Lit 3.0 相比 Lit 2.x 只有很少的破坏性变更:
- 不再支持 IE11。
- Lit 的 npm 模块现在以 ES2021 格式发布。
- Lit 2.x 版本中标记为已弃用的 API 已被移除。
- SSR 水合支持模块已移至
@lit-labs/ssr-client
包。 - 仅类型变更:
ReactiveElement
的renderRoot
和createRenderRoot()
的类型已更新。 - 移除了对 Babel 装饰器版本 "2018-09" 的支持。
- TypeScript 实验性装饰器和标准装饰器之间的行为已统一。
- 因此,如果你使用 TypeScript,你需要升级到至少 TypeScript v5.2,以获取两种装饰器的更新类型。
对于绝大多数用户,从 Lit 2 升级到 Lit 3 不需要任何代码更改。大多数应用和库可以扩展它们的 npm 版本范围以同时包含 2.x 和 3.x,例如 "^2.7.0 || ^3.0.0"
。
Lit 2.x 和 3.0 是可互操作的 - 一个版本的 Lit 的模板、基类和指令将与另一个版本的对应部分一起工作。
Lit 现在以 ES2021 格式发布
Permalink to "Lit 现在以 ES2021 格式发布"Lit 2 是以 ES2019 格式发布的,而 Lit 3 现在以 ES2021 格式发布,这在现代浏览器和构建工具中得到了广泛支持。 如果你需要支持较旧的浏览器版本并且你当前的工具无法解析 ES2021,这可能是一个破坏性变更。
使用 Webpack 4 和 Lit 3
Permalink to "使用 Webpack 4 和 Lit 3"Webpack 4 的内部解析器不支持空值合并运算符(??
)、逻辑赋值运算符(??=
)或可选链(?.
),这些是 ES2021 引入的语法,因此在遇到这些语法时会抛出 Module parse failed: Unexpected token
错误。
首选的解决方案是升级到支持解析这些较新 JS 语法的 Webpack 5。但是,如果你无法这样做,可以使用 babel-loader
来转换 Lit 3 代码,使其与 Webpack 4 兼容。
要在 Webpack 4 中转译 Lit 3,请安装以下必需的 babel 包:
> npm i -D babel-loader@8 \
@babel/plugin-transform-optional-chaining \
@babel/plugin-transform-nullish-coalescing-operator \
@babel/plugin-transform-logical-assignment-operators
并添加一个类似于以下的新规则(你可能需要根据你的特定项目进行修改):
// 在 webpack.config.js 中
module.exports = {
// ...
module: {
rules: [
// ... 你的其他规则
// 添加一个 babel-loader 规则,降级 Lit 的 ES2021 语法,以便 Webpack 4 可以解析它。
// TODO: 一旦升级到 Webpack 5,可以删除此规则。
{
test: /\.js$/,
include: ['@lit', 'lit-element', 'lit-html'].map((p) =>
path.resolve(__dirname, 'node_modules/' + p)
),
use: {
loader: 'babel-loader',
options: {
plugins: [
'@babel/plugin-transform-optional-chaining',
'@babel/plugin-transform-nullish-coalescing-operator',
'@babel/plugin-transform-logical-assignment-operators'
],
},
},
},
],
}
}
Lit 装饰器的更新
Permalink to "Lit 装饰器的更新"JavaScript 装饰器最近已被 TC39 标准化,并处于四阶段标准化过程的第 3 阶段。第 3 阶段是 JavaScript 实现(如虚拟机和编译器)开始实施稳定规范的阶段。TypeScript 5.2 和 Babel 7.23 最近已经实现了该标准。
这意味着装饰器 API 存在多个版本:标准装饰器、TypeScript 的实验性装饰器,以及 Babel 之前实现的提案,如版本 "2018-09"。
虽然 Lit 2 支持 TypeScript 实验性装饰器和 Babel 的 "2018-09" 装饰器,但 Lit 3 现在支持标准装饰器和 TypeScript 实验性装饰器。
Lit 3 装饰器大多向后兼容 Lit 2 TypeScript 装饰器 - 很可能不需要任何更改。
为了使 Lit 装饰器在实验性和标准装饰器模式之间的行为保持一致,一些小的破坏性变更是必要的。
Lit 3.0 中 Lit 装饰器行为的变化:
- 对于使用
@property()
和@state()
装饰的访问器,现在会自动调用requestUpdate()
,而之前这是由 setter 负责的。 - 访问器的值在首次渲染时被读取,并用作
changedProperties
和属性反射的初始值。 - Lit 3 装饰器不再支持
@babel/plugin-proposal-decorators
的version: "2018-09"
选项。Babel 用户应该迁移到标准装饰器。 - [可选]:我们建议将手写访问器的
@property()
和@state()
迁移到 setter,以帮助迁移到标准装饰器。
已移除 API 列表
Permalink to "已移除 API 列表"如果你的 Lit 2.x 项目没有弃用警告,你应该不会受到这个列表的影响。
- 移除了
UpdatingElement
作为ReactiveElement
的别名。 - 从主
lit-element
模块中移除了装饰器的重新导出。 - 移除了
queryAssignedNodes
装饰器的已弃用调用签名。 - 将实验性服务器端渲染水合模块从
lit
、lit-element
和lit-html
移动到@lit-labs/ssr-client
。
移除 UpdatingElement
作为 ReactiveElement
的别名
Permalink to "移除 UpdatingElement 作为 ReactiveElement 的别名" 将 Lit 2.x 中使用的 UpdatingElement
替换为 ReactiveElement
。这不是功能性变更,因为 UpdatingElement
是 ReactiveElement
的别名。
// 已移除
import {UpdatingElement} from 'lit';
// 更新后
import {ReactiveElement} from 'lit';
从 lit-element
中移除装饰器的重新导出
Permalink to "从 lit-element 中移除装饰器的重新导出" Lit 3.0 内置装饰器不再从 lit-element
导出,而应该从 lit/decorators.js
导入。
// 从 lit-element 中移除装饰器导出
import {customElement, property, state} from 'lit-element';
// 更新后
import {customElement, property, state} from 'lit/decorators.js';
已移除已弃用的 queryAssignedNodes(slot: string, flatten: bool, selector: string)
装饰器签名
Permalink to "已移除已弃用的 queryAssignedNodes(slot: string, flatten: bool, selector: string) 装饰器签名" 将任何使用带选择器的 queryAssignedNodes
的用法迁移为使用 queryAssignedElements
。
// 已移除
@queryAssignedNodes('list', true, '.item')
// 更新后
@queryAssignedElements({slot: 'list', flatten: true, selector: '.item'})
没有 selector
的用法现在必须使用选项对象。
// 已移除
@queryAssignedNodes('list', true)
// 更新后
@queryAssignedNodes({slot: 'list', flatten: true})
从 lit
、lit-element
和 lit-html
中移除服务器端渲染实验性水合模块
Permalink to "从 lit、lit-element 和 lit-html 中移除服务器端渲染实验性水合模块" 实验性水合支持已从核心库移出,并移至 @lit-labs/ssr-client
。
// 已移除
import 'lit/experimental-hydrate-support.js';
import {hydrate} from 'lit/experimental-hydrate.js';
// 更新后
import '@lit-labs/ssr-client/lit-element-hydrate-support.js';
import {hydrate} from '@lit-labs/ssr-client';
[仅类型]:已更新 renderRoot
和 createRenderRoot()
的类型
Permalink to "[仅类型]:已更新 renderRoot 和 createRenderRoot() 的类型" 这只是类型变更,没有运行时影响。
ReactiveElement.renderRoot
的类型从 Element | ShadowRoot
变更为 HTMLElement | DocumentFragment
,ReactiveElement.createRenderRoot()
的返回类型从 HTMLElement | ShadowRoot
变更为 HTMLElement | DocumentFragment
。这使它们与彼此以及 lit-html 的 render()
保持一致。
这个变更通常不会影响仅访问 this.renderRoot
的代码。但是,任何具有其先前类型的显式类型标注的代码都应该更新。
可选:升级到标准装饰器
Permalink to "可选:升级到标准装饰器"虽然 Lit 3 添加了对标准装饰器的支持,但我们仍然建议 TypeScript 用户继续使用实验性装饰器。这是因为目前 TypeScript 和 Babel 编译器为标准装饰器生成的代码相当庞大。
当浏览器支持标准装饰器,或者当我们在新的 Lit 编译器中发布装饰器转换支持时,我们将推荐在生产环境中使用标准装饰器。
但你现在可以尝试标准装饰器,它们在 TypeScript 5.2 及更高版本以及带有 @babel/plugin-proposal-decorators
插件的 Babel 7.23 中有效。
TypeScript
Permalink to "TypeScript"安装 TypeScript 5.2 或更高版本,并在 tsconfig 中 移除 "experimentalDecorators"
设置(如果存在)。
Babel
Permalink to "Babel"安装 Babel 7.23 或更高版本,以及 @babel/plugin-proposal-decorators
。确保向该插件传递 "version": "2023-05"
选项。
向装饰字段添加 accessor
关键字
Permalink to "向装饰字段添加 accessor 关键字" 标准装饰器不允许更改它们装饰的类成员的 类型。需要创建 getter 和 setter 的装饰器必须应用于现有的 getter 和 setter。为了使这更符合人体工程学,装饰器标准添加了 accessor
关键字,当应用于类字段时,它会创建"自动访问器"。自动访问器看起来和表现得很像类字段,但在原型上创建由私有存储支持的访问器。
@property()
、@state()
、@query()
、@queryAll()
、@queryAssignedElements()
和 @queryAssignedNode()
装饰器需要 accessor
关键字。
示例:
class MyElement extends LitElement {
@property()
accessor myProperty = "initial value"
...
}
将装饰器从 getter 移到 setter
Permalink to "将装饰器从 getter 移到 setter"标准装饰器只能替换它们直接应用的类成员。Lit 装饰器需要拦截属性设置,因此装饰器必须应用于 setter。这与 Lit 2 推荐的将装饰器应用于 getter 不同。
对于 @property()
和 @state()
,你还可以移除 setter 中的任何 this.requestUpdate()
调用,因为现在会自动执行此操作。如果你需要 不 调用 requestUpdate()
,你必须使用 noAccessor
属性选项。
请注意,对于 @property()
和 @state()
,装饰器在设置属性时会调用 getter 以检索旧值。这意味着你 必须 同时定义 getter 和 setter。
之前:
class MyElement extends LitElement {
private _foo = 42;
set(v) {
const oldValue = this._foo;
this._foo = v;
this.requestUpdate('foo', oldValue);
}
@property()
get() {
return this._foo;
}
}
之后:
class MyElement extends LitElement {
private _foo = 42;
@property()
set(v) {
this._foo = v;
}
get() {
return this._foo;
}
}