书,能保持我们的童心;书能保持我们的青春。——严文井

官方文档:https://www.typescriptlang.org/docs/handbook/decorators.html

这个东西在java里叫注解,不过在ts中,一个装饰器对应一个方法

首先执行命令:

1
tsc --target ES5 --experimentalDecorators

然后配置一下tsconfig.json就可以使用了

1
2
3
4
5
6
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}

首先我们定义一个class

1
2
3
4
class User {
private id: Number | undefined;
private name: string | undefined;
}

我们编写一个装饰器对应的逻辑,实现javalombok@Data生成gettersetter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Data(constructor: Function | ObjectConstructor) {
console.log('@Data', { constructor })
Object.getOwnPropertyNames(new constructor()).forEach(key => {
const firstUpperProperty = key.charAt(0).toUpperCase() + key.substring(1)
if (!constructor.prototype[`get${firstUpperProperty}`]) {
constructor.prototype[`get${firstUpperProperty}`] = function () {
return this[key]
}
}
if (!constructor.prototype[`set${firstUpperProperty}`]) {
constructor.prototype[`set${firstUpperProperty}`] = function (value: any) {
this[key] = value
}
}
})
}

我们将这个注解放到类的上面

1
2
3
4
5
@Data
class User {
private id: Number | undefined;
private name: string | undefined;
}

尝试调用一下

1
2
3
4
5
const user = new User()
user.setId(1);
user.setName("John");
console.log(user.getId()); // 1
console.log(user.getName()); // "John"

可以看到确实生效,这里打印的参数:

image-20220621184302118

顺带一提注解可以以复数形式存在,上方文档提到了,这里就不多赘述

我们继续编写注解,刚刚这个是类装饰器

接下来来个给默认的getter方法获取时,如果没有就提供一个默认值的注解@DefaultVal

这是一个属性装饰器

1
2
3
4
5
6
7
8
9
function DefaultVal(val: any) {
return function (target: any, key: string) {
console.log('@DefaultVal', { target, key, val });
const firstUpperProperty = key.charAt(0).toUpperCase() + key.substring(1)
target.constructor.prototype[`get${firstUpperProperty}`] = function () {
return this[key] ?? val
}
};
}

注意优先级,我们的属性装饰器优先级高于我们的类装饰器,所以getter别被覆盖了

类中不同声明上的装饰器将按以下规定的顺序应用:

  1. 参数装饰器,然后依次是方法装饰器访问符装饰器,或属性装饰器应用到每个实例成员。
  2. 参数装饰器,然后依次是方法装饰器访问符装饰器,或属性装饰器应用到每个静态成员。
  3. 参数装饰器应用到构造函数。
  4. 类装饰器应用到类。

使用方式:

1
2
3
4
5
6
@Data
class User {
private id: Number | undefined;
@DefaultVal('nobody')
private name: string | undefined;
}

演示:

1
console.log({ 'new User().getName()': new User().getName() });

输出结果:

1
{'new User().getName()': 'nobody'}

打印参数:

image-20220621184325477

然后是方法装饰器

这里我们指定返回值不能为null,否则报错

1
2
3
4
5
6
7
8
9
10
11
function NonNull() {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log('@NonNull', { target, propertyKey, descriptor });
let value = descriptor.value
descriptor.value = () => {
const result = value.call()
if (result === null || result === undefined) throw new Error(`${propertyKey} must non-null`)
return result
}
};
}

使用:

1
2
3
4
5
6
7
8
9
10
11
@Data
class User {
private id: Number | undefined;
@DefaultVal('nobody')
private name: string | undefined;
@NonNull()
toString() {
// return Object.prototype.toString();
return null
}
}

测试:

1
new User().toString();

image-20220621184052422

打印结果:

image-20220621184340137

最后是参数装饰器

这里懒得写了,就输出下日志吧:

1
2
3
function Log(target: Object, propertyKey: string | symbol, parameterIndex: number) {
console.log('@Log', { target, propertyKey, parameterIndex });
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Data
class User {
private id: Number | undefined;
@DefaultVal('nobody')
private name: string | undefined;
@NonNull()
toString() {
return Object.prototype.toString();
// return null
}
equals(@Log val: User) {
return deepEqual(this, val);
}
}

打印:

image-20220621184455024