前言
类装饰器不常用。
本节主要介绍装饰器的相关语法,以及一些需要注意的细节。
关键字
类装饰器、experimentalDecorators
参考资料
参数
类装饰器的本质是一个函数,该函数接收一个参数,表示该类本身(构造函数本身)。
语法
使用装饰器的语法:@得到一个函数
变量约束为类
在 TS 中,如何约束一个变量为类:
Function
new (参数) => object
「推荐」
执行时间
装饰器函数的运行时间:在类定义后直接运行
返回值
类装饰器可能的返回值:
- void:仅运行函数
- 返回一个新的类:会将新的类替换掉装饰目标
多个装饰器
执行顺序:会按照后加入先调用的顺序进行调用。
experimentalDecorators
Experimental support for decorators is a feature that is subject to change in a future release. Set the ‘experimentalDecorators’ option in your ‘tsconfig’ or ‘jsconfig’ to remove this warning.
对装饰器的实验支持是一项在将来版本中更改的功能。设置 experimentalDecorators
选项以删除此警告。
将
experimentalDecorators
选项设置为 true,可以移除 vscode 中的警告信息。 问题描述:设置的experimentalDecorators
不生效,如下图所示:解决办法:打开「设置」,搜索「experimental decorators」(参考资料:链接)
![]()
codes
在 TS 中,约束一个变量为类的两种写法:
Function
new (参数) => object
function test(target: Function) {
// ...
new target();
// ...
}
@test
class A { }
虽然写 Function 也是 OK 的,但是如果在函数体 test 中,通过 new 关键字来调用 test 的话,那么会报错。
function test(target: new () => object) {
// ...
new target();
// ...
}
@test
class A { }
function test(target: new () => object) {
console.log(target); // => [class A]
}
@test
class A { }
const a = new A();
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length,
r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc,
d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else
for (var i = decorators.length - 1; i >= 0; i--)
if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
function test(target) {
console.log(target);
}
let A = class A {};
A = __decorate([
test
], A);
const a = new A();
编辑结果分析:
编译结果比较复杂,不过仔细观察,主要由几个部分:
- __decorate 函数
- test 函数
- A 类
__decorate 函数
__decorate 函数接收的参数:(decorators, target, key, desc)
核心关注前面两个:
- decorators:所有的装饰器
- target:这些装饰器作用的目标
decorate 函数的调用:`decorate([ test ], A)`
[ test ]
:它会将所有的装饰器合并到一个数组中,作为它的第一个参数传入,这里我们只使用到一个 test 装饰器,所以数组中只有一个成员 test。A
:这些装饰器作用的目标是一个类,将这个类作为第二个参数传入。
__decorate 函数的返回值是一个经过装饰器处理后的新的构造函数。
test 函数
test 函数是一个装饰器
A 类
A 最终的值将会是 __decorate 的返回值。
function test(target: new () => object) {
return class B {};
}
@test
class A {}
const a = new A();
console.log(a); // => B {}
装饰器 test 的返回值,就是 A()
得到的结果。new A()
和 new B()
是等效的。
function test(target: new () => object) {
return class B extends target {};
}
@test // ×
class A {
prop1: string;
}
const a = new A();
console.log(a.prop1);
装饰函数返回类型“typeof B”不能赋给类型“void | typeof A”。类型“typeof B”不可赋给类型“typeof A”。类型“{ 0 }”中缺少属性“prop1”,但类型“A”中需要该属性。
return class B extends target {}
虽然 A 中确实含有 prop1 属性,按理来说,B 继承 A,那么 B 自然也有 prop1 属性,所以上述程序,直接看来是没有隐患的。但是 test 装饰器的参数 target 是动态的,它不一定就是 A。
这种写法,也就是在学习的时候会介绍介绍,实际开发中也不会这么写,需要知道这么写会导致的问题就可以了。
function test(target: new () => object) {
}
@test // ×
class A {
prop1: string
constructor (public prop2: string) {
}
}
“typeof A”类型的参数不能赋给“new () => object”类型的参数。构造签名的类型不兼容。类型“new (prop2: string) => A”不能赋给类型“new () => object”。
test 装饰器的参数 target 约束不满足条件,所以会报错,需要修改 target 的约束条件。
new () => object
:不能接收参数new (...args: any[]) => object
:可以接收若干个参数
function test(des: string) {
return function (target: new (...args: any[]) => object) {
// ...
};
}
@test('这是一个类')
class A {
prop1: string;
}
@test('这是一个类')
返回值是一个装饰器
type constructor = new (...args: any[]) => object;
function d1(target: constructor) {
console.log("d1");
}
function d2(target: constructor) {
console.log("d2");
}
@d1
@d2
class A {
prop1: string;
}
若出现多个装饰器,执行顺序会按照后加入先调用的顺序进行调用:
先输出 d2
再输出 d1
type constructor = new (...args: any[]) => object;
function d1() {
console.log("d1");
return function (target: constructor) {
console.log("d1 decorator");
}
}
function d2() {
console.log("d2");
return function (target: constructor) {
console.log("d2 decorator");
}
}
@d1()
@d2()
class A {
prop1: string;
}
打印结果:
d1
d2
d2 decorator
d1 decorator
d1 和 d2 是普通的函数,它们的返回值才是装饰器。
普通的函数,写在前边的先执行。所以会先执行 d1 再执行 d2
构造器函数,写的位置与类的距离越近,越先执行。所以会先执行装饰器 d2() 再执行装饰器 d1()