通过本文你可以知道什么
- 装饰器的发展历程
- JS和TS装饰器有何不同
- Angular中的装饰器到底是什么
- 装饰器的定义,语法,作用
- reflect-meta是什么及如何使用
前言
我们平常开发中或多或少的听说或使用过装饰器,也切身感受到了它带给我们的便利。但是应该很少去系统的了解过装饰器。不清楚装饰器到底擅长干什么,怎么干。
由于目前js和ts中的装饰器有很多不同,本期只聚焦于ts的装饰器进行探讨。
本文预计阅读时间——20分钟
装饰器的演变
- 2015-3-24
- stage 1阶段,也是目前广为使用的用法,也基本等同于TS开启了experimentalDecorators的用法。
- 2018-09
- 进入到stage2阶段,用法和stage1很大不同
- 2021-12
- 针对stage2提案进行了一次修改。
- 2022-03
- 正式进入stage3。去掉了metadata部分,使用方式没有发生太大变化。
冷知识:ts只会对Stage-3以上的提案提供支持,而TS引入装饰器实在2015年3月,差不多stage-1的时间段,这是因为在 NG-Conf上,angular团队宣布与TS团队进行合作。
JS装饰器和TS装饰器
js原生目前不支持装饰器,装饰器提案在stage-3阶段,只能通过babel体验装饰器这个新特性。TS目前实现的装饰器是基于JS装饰器stage-1的语法,所以在JS装饰器正式发布后,会和TS装饰器语法产生差异,之后看TS团队如何处理了,但预计也不是近期的事情了。
定义
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,访问符,属性或参数上。装饰器使用@expression这种形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息作为参数传入。
定义来自:https://www.tslang.cn/docs/handbook/decorators.html
配置
由于装饰器目前还是实验中的特定,在js中处于stage-3阶段。在ts中已经作为一项实验性予以支持。开启装饰器需要在tsconfig.json文件中启用 experimentalDecorators 编译器选项。
装饰器于2022年三月底刚进入了stage-3阶段,详情见https://github.com/tc39/proposal-decorators/pull/454
Angular中的装饰器
我们在使用angular中经常会看到此类代码
每个指令,组件,module都会有对应的@expression进行标注,完全吻合装饰器的写法。但其实这种@Component类似的写法不能称作装饰器,更贴切的叫法为注解(Annotation)。它们是用于给编译器做数据描述,最终在build阶段会完全被抹去。
注解并不产生任何行为,仅仅添加附加内容。
装饰器使用
类装饰器
类装饰器是我们最常使用到的,它的通常作用是,为该类扩展功能
- 类装饰器有且只有一个参数,参数为类的构造函数constructor
- 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明
如果你要返回一个新的构造函数,你必须注意处理好原来的原型链。 在运行时的装饰器调用逻辑中不会为你做这些。—— 官方文档
设想有这样一个场景。
目前有一个Tank类,有一个Plane类,有一个Animal类。这三个类都需要一个公共的方法来获取他们所在的位置。我们第一可能想到使用继承来实现。
class BaseClass {
getPosition() {
return {
x: 100,
y: 200,
z: 300,
}
}
}
class Tank extends BaseClass{
}
class Plane extends BaseClass {
}
class Animal extends BaseClass {
}
这样三个类都可以调用getPosition
方法来获取各自的位置了。到目前为止看起来没什么问题。
现在又有了一个新的诉求,Tank 类和Plane类需要一个新的方法addPetrol
来给坦克和飞机加油。而动物不需要加油。此时这种写法好像不能继续进行下去了。而js目前没有直接语法提供多继承的功能,我们的继承方式好像行不通了。这时候装饰器可以很完美的实现这样的功能。此时就可以请我们的装饰器闪亮登场了~
装饰器功能之——能力扩展
我们把getPosition
和addPertrol
都抽象成一个单独的功能,它们得作用是给宿主扩展对应的功能。
const getPositionDecorator: ClassDecorator = (constructor: Function) => {
constructor.prototype.getPosition = () => {
return [100, 200]
}
}
const addPetrolDecorator: ClassDecorator = (constructor: Function) => {
constructor.prototype.addPetrol = () => {
// do something
console.log(`${
constructor.name}进行加油`);
}
}
@addPetrolDecorator
@getPositionDecorator
class Tank {
}
@addPetrolDecorator
@getPositionDecorator
class Plane {
}
@getPositionDecorator
class Animal {
}
这样的话,加入日后我们有其他的猫猫狗狗,都可以对他进行能力扩展,让其具有加油
的能力。
多个装饰器叠加的时候,执行顺序为离被装饰对象越近的装饰器越先执行。
装饰器功能之——重载构造函数
在类装饰器中如果返回一个值,它会使用提供的构造函数来替换类的声明。
function classDecorator<T extends {
new(...args:any[]):{
}}>(constructor:T) {
return class extends constructor {
newProperty = "new property";
hello = "override";
}
}
@classDecorator
class Greeter {
property = "property";
hello: string;
constructor(m: string) {
this.hello = m;
}