装饰器

记录一下学习装饰器的过程。

参考文档 https://www.typescriptlang.org/docs/handbook/decorators.html
参考博客 https://saul-mirone.github.io/zh-hans/a-complete-guide-to-typescript-decorator/

先讲要点

装饰器包括:

  1. 类装饰器
  2. 类方法装饰器
  3. 类属性装饰器
  4. 类参数装饰器
  5. 类访问器装饰器

功能:

  1. 增加与修改类成员。
  2. 为类方法扩展新功能
  3. 通过和reflect-metadata库一起使用,可以在对类实例化之前获得成员类型。
  4. 通过和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
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// ------------------- 类装饰器 -----------
/**
* 类装饰器
* @param constructor - 被装饰的类
* @retruns 装饰后的类
*/
function ClassDecorator<T extends { new (...args: any[]): {} }>(
constructor: T
) {
return class extends constructor {
// ... 添加装饰内容
newVal = 100;
print() {
console.log(this.newVal);
}
};
}

/**
* 类装饰器工厂,可以传参
* @param variable
* @returns
*/
function ClassDecoratorFactory(variable: any) {
return ClassDecorator;
}
// --------- test ------------

@ClassDecorator
class A {
newVal!: number;
val = 10;

print!: () => void;
}

const a = new A();
a.print(); // 100

方法装饰器

方法装饰器,参数

  1. target: 对于静态成员来说是类的构造器,对于实例成员来说是类的原型链。
  2. propertyKey: 属性的名称。
  3. descriptor: 属性的描述器。

方法装饰器通常用来为被装饰的方法提供额外的功能。

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
// 日志功能.
/**
* 方法装饰器
* @returns
*/

function LoggerDecorator(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
// 备份原方法
const oldMethod: Function = descriptor.value;

descriptor.value = function () {
console.log("logger", "start");
// 注意this指向问题
oldMethod.apply(this, arguments);
console.log("loager", "end");
};
}
// test

class A {
@LoggerDecorator
exec() {
console.log("exec");
}
}

const a = new A();
a.exec();
/*
logger start
exec
logger end
*/

访问器装饰器

三个参数参照方法装饰器。

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
// 这是给访问器,加后缀的例子
/**
*
* @param value - 后缀名
* @returns
*/
function Suffix(value: string) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const getter = descriptor.get;
if (getter) {
descriptor.get = function () {
return getter.call(this) + value;
};
}
};
}
// test

class A {
_width = 100;

@Suffix('px');
get width() {
return this._width;
}
}

const a = new A;
console.log(a.width); // 100px;

参数装饰器

参数装饰器需要借助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
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
55
56
// 将document.body注入方法中。配合一个方法选则器使用。
// 参数选则器确认参数位置。方法选则器负责注入。

import "reflect-metadata";

// 创建一个元数据的key
const bodyMetaDataKey = Symbol("body");

function body(
target: Object,
propertyKey: string | symbol,
parameterIndex: number
) {
const argumentsMap =
Reflect.getOwnMetadata(bodyMetaDataKey, target, propertyKey) || {};

argumentsMap[parameterIndex] = document.body;
// 将元数据存入对应propertyKey,对象的键为参数的位置。
Reflect.defineMetadata(bodyMetaDataKey, argumentsMap, target, propertyKey);
}

function bodyDecorator(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
let method = descriptor.value;

descriptor.value = function () {
// 取出元数据
let argumentsMap: Record<string, any> = Reflect.getMetadata(
bodyMetaDataKey,
target,
propertyKey
);

const argvs = [...arguments];
// 将元数据存入对应的参数中
Object.keys(argumentsMap).forEach((key) => {
argvs[Number(key)] = argumentsMap[key];
});

return method.apply(this, argvs);
};
}

// test
class A {
@bodyDecorator
run(@body body: HTMLElement) {
console.log(body);
}
}

const a = new A();
a.run(document.createElement('div')); // <body>

属性装饰器

可以借助reflect-metadata收集属性的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'reflect-metadata';

function Property(target: any, propertyKey: string) {
const type = Reflect.getMetadata('design:type', target, propertyKey);
console.log(type);
}

class A {
@Property
name!: string;

}

// test
// 详细输出参考文章开头列出的第二个网站中的元数据部分。