装饰器概念

装饰器-DecoratorsTypeScript 中是一种可以在不修改类代码的基础上通过添加标注的方式来对类型进行扩展的一种方式

  • 减少代码量
  • 提高代码扩展性、可读性和维护性

TypeScript 中,装饰器只能在类中使用

装饰器的使用

使用装饰器需要配置文件中启用experimentalDecorators: true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 定义方法装饰器
function log(target: Function, name: string, descriptor: PropertyDescriptor) {
/**
* target : 被装饰的方法所属的类
* name : 当前被装饰方法的名称
* descriptor : 描述符
*/
// 将原始方法提取出来
let fn = descriptor.value
// 定义新的方法
descriptor.value = function (a: number, b: number) {
// 调用原来的方法
const result = fn(a, b)
// 增加新的方法
console.log(`${a}+${b}=${result}`)
// 将结果返回
return result
}
}

class M {
@log
static add(a: number, b: number) {
return a + b
}
}

let v1 = M.add(1, 2)
console.log(v1)

image-20201215114331917

装饰器

装饰器 是一个函数,它可以通过 @装饰器函数 这种特殊的语法附加在 方法访问符属性参数 上,对它们进行包装,然后返回一个包装后的目标对象(方法访问符属性参数 ),装饰器工作在类的构建阶段,而不是使用阶段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function 装饰器1() {}
...

@装饰器1
class MyClass {

@装饰器2
a: number;

@装饰器3
static property1: number;

@装饰器4
get b() {
return 1;
}

@装饰器5
static get c() {
return 2;
}

@装饰器6
public method1(@装饰器5 x: number) {
//
}

@装饰器7
public static method2() {}
}

装饰器的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
function d1(target: Function) {
console.log(typeof target, target);
}
function d11(target: Function) {
console.log(typeof target, target, '1111111');
}

function d2(target: any, name: string) {
console.log(typeof target, name);
}
function d3(target: any, name: string, descriptor: PropertyDescriptor) {
console.log(typeof target, name, descriptor);
}
function d4(target: any, name: string, descriptor: PropertyDescriptor) {
console.log(typeof target, name, descriptor);
}
function d5(target: any, name: string, index: number) {
// name : 当前参数所在的方法
console.log(typeof target, name, index);
}

@d1
@d11
class MyClass {

@d2
static property1: number;

@d2
a: number;

@d3
get b() {
return 1;
}

@d3
static get c() {
return 2;
}

@d4
public method1(@d5 x: number, @d5 y: number) {}

@d4
public static method2() {}

}
装饰器目标第一个参数第二个参数第三个参数
类装饰器应用于类的构造函数类的构造函数作为其唯一的参数××
方法装饰器应用于类的方法上静态方法:类的构造函数
实例方法:类的原型对象
方法名称方法描述符对象
属性装饰器应用于类的属性上静态方法:类的构造函数
实例方法:类的原型对象
属性名称×
访问器装饰器应用于类的访问器(gettersetter)上静态方法:类的构造函数
实例方法:类的原型对象
属性名称方法描述符对象
参数装饰器应用在参数上静态方法:类的构造函数
实例方法:类的原型对象
方法名称参数在函数参数列表中的索引

装饰器的执行顺序

image-20201215141610672

装饰器工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
function log(type: string) {
return function (target: Function, name: string, descriptor: PropertyDescriptor) {

let value = descriptor.value;
descriptor.value = function(x: number, y: number) {
let result = value(x, y);

console.log({
type,
name,
x,
y,
result
});

return result;
}

}
}

class M {
@log('log')
static add(x: number, y: number) {
return x + y;
}

@log('storage')
static sub(x: number, y: number) {
return x - y;
}
}

let v1 = M.add(1, 2);
console.log(v1);
let v2 = M.sub(1, 2);
console.log(v2);

export default {}

元数据

使用 reflect-metadata

https://www.npmjs.com/package/reflect-metadata

首先,需要安装 reflect-metadata

1
npm install reflect-metadata

定义元数据

我们可以 方法 等数据定义元数据

  • 元数据会被附加到指定的 方法 等数据之上,但是又不会影响 方法 本身的代码

设置

Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey)

  • metadataKey:meta 数据的 key
  • metadataValue:meta 数据的 值
  • target:meta 数据附加的目标
  • propertyKey:对应的 property key

调用方式

  • 通过 Reflect.defineMetadata 方法调用来添加 元数据

  • 通过 @Reflect.metadata 装饰器来添加 元数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import "reflect-metadata"

@Reflect.metadata("n", 1)
class A {
@Reflect.metadata("n", 2)
public static method1() {
}

@Reflect.metadata("n", 4)
public method2() {
}
}

// or
Reflect.defineMetadata('n', 1, A);
Reflect.defineMetadata('n', 2, A, 'method1');

let obj = new A();
Reflect.defineMetadata('n', 3, obj);
Reflect.defineMetadata('n', 4, obj, 'method2');

console.log(Reflect.getMetadata('n', A));
console.log(Reflect.getMetadata('n', A, ));

获取

Reflect.getMetadata(metadataKey, target, propertyKey)

参数的含义与 defineMetadata 对应

使用元数据的 log 装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import "reflect-metadata"

function L(type = 'log') {
return function(target: any) {
Reflect.defineMetadata("type", type, target);
}
}
// 装饰器函数
function log(callback: Function) {
return function(target: any, name: string, descriptor: PropertyDescriptor) {
let value = descriptor.value;

let type = Reflect.getMetadata("type", target);

descriptor.value = function(a: number, b: number) {
let result = value(a, b);
if (type === 'log') {
console.log('日志:', {
name,
a,
b,
result
})
}
if (type === 'storage') {
localStorage.setItem('storageLog', JSON.stringify({
name,
a,
b,
result
}));
}
return result;
}
}
}

// 原始类
@L('log')
class M {
@log
static add(a: number, b: number) {
return a + b;
}
@log
static sub(a: number, b: number) {
return a - b;
}
}

let v1 = M.add(1, 2);
console.log(v1);
let v2 = M.sub(1, 2);
console.log(v2);

使用 emitDecoratorMetadata

tsconfig.json 中有一个配置 emitDecoratorMetadata,开启该特性,typescript 会在编译之后自动给 方法访问符属性参数 添加如下几个元数据

  • design:type:被装饰目标的类型
    • 成员属性:属性的标注类型
    • 成员方法:Function 类型
  • design:paramtypes
    • 成员方法:方法形参列表的标注类型
    • 类:构造函数形参列表的标注类型
  • design:returntype
    • 成员方法:函数返回值的标注类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import "reflect-metadata"

function n(target: any) {
}
function f(name: string) {
return function(target: any, propertyKey: string, descriptor: any) {
console.log( 'design type', Reflect.getMetadata('design:type', target, propertyKey) );
console.log( 'params type', Reflect.getMetadata('design:paramtypes', target, propertyKey) );
console.log( 'return type', Reflect.getMetadata('design:returntype', target, propertyKey) );
}
}
function m(target: any, propertyKey: string) {

}

@n
class B {
@m
name: string;

constructor(a: string) {

}

@f('')
method1(a: string, b: string) {
return 'a'
}
}

编译后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
__decorate([
m,
__metadata("design:type", String)
], B.prototype, "name", void 0);
__decorate([
f(''),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, String]),
__metadata("design:returntype", void 0)
], B.prototype, "method1", null);
B = __decorate([
n,
__metadata("design:paramtypes", [String])
], B);