装饰器
装饰器是可以用来声明式地注解和修改类行为的函数。
Lit 提供了一组可选的装饰器,为注册元素、定义响应式属性和查询属性,或向事件处理方法添加事件选项等功能提供声明式 API。
例如,@customElement
和 @property()
装饰器让你可以以紧凑、声明式的方式注册自定义元素并定义响应式属性:
@customElement('my-element')
export class MyElement extends LitElement {
@property()
greeting = 'Welcome';
}
Lit 支持 JavaScript 装饰器提案的两个不同版本 – TypeScript 支持的早期版本,我们称之为_实验性装饰器_,以及我们称之为_标准装饰器_的新的最终版本。
这两个提案在使用上有一些小差异(标准装饰器通常需要使用 accessor
关键字)。我们的代码示例是为实验性装饰器编写的,因为我们目前建议在生产环境中使用它们。
查看装饰器版本了解更多详情。
内置装饰器
Permalink to "内置装饰器"装饰器 | 摘要 | 更多信息 |
---|---|---|
@customElement | 定义自定义元素。 | 定义 |
@eventOptions | 添加事件监听器选项。 | 事件 |
@property | 定义公共属性。 | 属性 |
@state | 定义私有状态属性 | 属性 |
@query | 定义返回组件模板中元素的属性。 | Shadow DOM |
@queryAll | 定义返回组件模板中元素列表的属性。 | Shadow DOM |
@queryAsync | 定义返回解析为组件模板中元素的 promise 的属性。 | Shadow DOM |
@queryAssignedElements | 定义返回分配给特定插槽的子元素的属性。 | Shadow DOM |
@queryAssignedNodes | 定义返回分配给特定插槽的子节点的属性。 | Shadow DOM |
导入装饰器
Permalink to "导入装饰器"你可以通过 lit/decorators.js
模块导入所有 Lit 装饰器:
import {customElement, property, eventOptions, query} from 'lit/decorators.js';
为了减少运行组件所需的代码量,装饰器可以单独导入到组件代码中。所有装饰器都可在 lit/decorators/<decorator-name>.js
获得。例如,
import {customElement} from 'lit/decorators/custom-element.js';
import {eventOptions} from 'lit/decorators/event-options.js';
启用装饰器
Permalink to "启用装饰器"要使用装饰器,你需要使用编译器如 TypeScript 或 Babel 构建你的代码。
将来当浏览器原生支持装饰器时,这将不再是必需的
使用 TypeScript 的装饰器
Permalink to "使用 TypeScript 的装饰器"TypeScript 同时支持实验性装饰器和标准装饰器。我们建议 TypeScript 开发者目前使用实验性装饰器,以获得最佳编译器输出。如果你的项目需要使用标准装饰器或设置 "useDefineForClassFields": true
,请跳到迁移到标准装饰器。
要使用实验性装饰器,你必须启用 experimentalDecorators
编译器选项。
你还应确保 useDefineForClassFields
设置为 false
。仅当 target
设置为 ES2022
或更高时才需要这样做,但建议显式设置为 false
。这是为了避免在声明属性时出现类字段问题。
// tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true,
"useDefineForClassFields": false,
}
}
启用 emitDecoratorMetadata
不是必需的,也不推荐。
将 TypeScript 实验性装饰器迁移到标准装饰器
Permalink to "将 TypeScript 实验性装饰器迁移到标准装饰器"Lit 装饰器设计为在 TypeScript 的实验性装饰器模式下支持标准装饰器语法(在类字段装饰器上使用 accessor
)。
这允许逐步从实验性装饰器迁移,从向装饰属性添加 accessor
关键字开始,而不改变行为。一旦所有装饰的类字段都使用了 accessor
关键字,你可以更改编译器选项来完成向标准装饰器的迁移:
// tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": false, // TypeScript 5.0 及更高版本的默认值
"useDefineForClassFields": true, // 当 "target" 为 "ES2022" 或更高时的默认值
}
}
注意:accessor
关键字在 TypeScript 4.9 中引入,带有元数据的标准装饰器需要 TypeScript ≥5.2。
使用 Babel 的装饰器
Permalink to "使用 Babel 的装饰器"Babel 从 7.23 版开始支持带有 @babel/plugin-proposal-decorators
插件的标准装饰器。Babel 不支持 TypeScript 实验性装饰器,所以你必须使用带有 accessor
关键字的标准装饰器语法来使用 Lit 装饰器在装饰的类字段上。
通过添加 @babel/plugin-proposal-decorators
并使用这些 Babel 配置设置来启用装饰器:
// babel.config.json
{
"plugins": [
["@babel/plugin-proposal-decorators", {"version": "2023-05"}]
]
}
注意:Lit 装饰器只能与 "version": "2023-05"
一起使用。其他版本,包括以前支持的 "2018-09"
,都不受支持。
装饰器版本
Permalink to "装饰器版本"装饰器是一个第 3 阶段提案,用于添加到 ECMAScript 标准中。像 Babel 和 TypeScript 这样的编译器支持装饰器,虽然目前还没有浏览器实现它们。Lit 装饰器适用于 Babel 和 TypeScript,并且当浏览器原生实现它们时也能正常工作。
第 3 阶段是什么意思?
这意味着规范文本已经完成,并准备好让浏览器实现。一旦规范在多个浏览器中实现,它可以进入最终阶段,即第 4 阶段,并被添加到 ECMAScript 标准中。第 3 阶段提案只有在实现过程中发现关键问题时才会改变。
早期装饰器提案
Permalink to "早期装饰器提案"在 TC39 提案达到第 3 阶段之前,编译器实现了装饰器规范的早期版本。
其中最著名的是 TypeScript 的_实验性装饰器_,Lit 自成立以来一直支持它,并且是我们目前推荐使用的。
Babel 也随着时间的推移支持了规范的不同版本,这可以从装饰器插件的 "version"
选项中看出。过去,Lit 2 支持 Babel 用户使用 "2018-09"
版本,但现在已经放弃了这个版本,转而支持下面描述的_标准_ "2023-05"
版本。
标准装饰器
Permalink to "标准装饰器"_标准装饰器_是在 TC39(定义 ECMAScript/JavaScript 的机构)达成第 3 阶段共识的装饰器版本。
标准装饰器在 TypeScript 和 Babel 中得到支持,而原生浏览器支持将在不久的将来到来。
标准装饰器和实验性装饰器之间最大的区别是,出于性能原因,标准装饰器不能改变被装饰和替换的类成员的_类型_ – 字段、访问器和方法 – 而只会产生相同类型的成员。
由于许多 Lit 装饰器生成访问器,这意味着装饰器需要应用于访问器,而不是类字段。
为了方便使用,标准装饰器规范添加了 accessor
关键字来声明"自动访问器":
class MyClass {
accessor foo = 42;
}
自动访问器创建一个 getter 和 setter 对,用于从私有字段读取和写入。然后装饰器可以包装这些 getter 和 setter。
在实验性装饰器中应用于类字段的 Lit 装饰器 – 如 @property()
、@state()
、@query()
等 – 在标准装饰器中必须应用于访问器或自动访问器:
@customElement('my-element')
export class MyElement extends LitElement {
@property()
accessor greeting = 'Welcome';
}
编译器输出考虑
Permalink to "编译器输出考虑"不幸的是,由于需要生成访问器、私有存储和装饰器 API 的其他对象,标准装饰器的编译器输出相当庞大。
因此,我们建议希望使用装饰器的用户,如果可能的话,暂时使用 TypeScript 实验性装饰器。
未来,Lit 团队计划在我们的可选 Lit 编译器中添加装饰器转换,以便将标准装饰器编译为更紧凑的编译器输出。原生浏览器支持也将完全消除对任何编译器转换的需求。