书,能保持我们的童心;书能保持我们的青春。——严文井
官方文档: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; }
|
我们编写一个装饰器对应的逻辑,实现java
中lombok
里@Data
生成getter
和setter
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()); console.log(user.getName());
|
可以看到确实生效,这里打印的参数:
顺带一提注解可以以复数形式存在,上方文档提到了,这里就不多赘述
我们继续编写注解,刚刚这个是类装饰器
接下来来个给默认的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 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'}
|
打印参数:
然后是方法装饰器
这里我们指定返回值不能为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 null } }
|
测试:
打印结果:
最后是参数装饰器
这里懒得写了,就输出下日志吧:
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(); } equals(@Log val: User) { return deepEqual(this, val); } }
|
打印: