上篇文章有提到装饰器,但是没有过多解释,这篇文章主要讨论装饰器,官方文档有详细的解释,希望我能给大家多提供一个视角来观察装饰器。这里不会面面俱到的把文档整理过来,这里尽量做到简洁实用,会用及在项目中使用之后,如果遇到复杂的问题不能解决,去官方文档或者有针对性的搜索,应该更好。
如何启用?
因为装饰器还是JavaScript里的实验性特性,所以必须在配置文件里启用它,一般在根目录下建tsconfig.json文件,必须包含以下内容:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
如何使用?
装饰器主要分五类,下面一一介绍:
1. 类装饰器
const log = console.log
function setClassName (name: string) {
return function <T extends { new (...args: any[]): {}}>(constructor: T) {
return class extends constructor {
__name = name
}
}
}
@setClassName('Teacher')
export default class Teacher { }
const teacher = new Teacher()
log({ Teacher, teacher })
log(teacher['__name'])
上面代码定义了一个setClassName装饰器,代码很简单,理解上应该没有难度。装饰器其实就普通的函数,只不过有一些规则。比如类装饰器接受唯一的一个参数,参数就是类本身。
该示例可以适用于,代码被压缩了,但是你想在运行时获取类名,装饰器用这种方式满足了你的需求。
2. 方法装饰器
function interceptor (before: Function, after: Function) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const value = descriptor.value as Function
function fun (this: any, ...args: any[]) {
before.apply(this, args)
const res = value.apply(this, args)
after.apply(this, [...args, res])
return res
}
descriptor.value = fun
}
}
export default class Teacher {
salary: number = 10000
bonus: number = 2000
@interceptor((date:string) => log(`计算${date.getMonth() + 1}份薪资`), (date: string, res: number) => log('薪资支付完成:' + res))
pay (date: string) {
return this.salary + this.bonus
}
}
const teacher = new Teacher()
teacher.pay('2020-11-11')
执行pay函数,会先后打印“计算2020-11-11薪资”和“薪资支付完成:12000”。
参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 成员的属性描述符。
示例作用:
使用interceptor装饰器,可以在函数执行前做一些操作,比如,弹窗提示,比如埋点、比如参数拦截等等。也可以在函数执行后做一些处理,比如提示,结果格式化等等。
3. 访问器装饰器
function test (val: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
log(val, target, propertyKey, descriptor)
}
}
@setClassName('Teacher')
export default class Teacher {
_age: number = 18
@test(false)
get age () {
return this._age
}
set age (val: number) {
this._age = val
}
}
访问器装饰器写法同方法装饰器一样,参数也一样,这里没想到合理的需求,写法上就这样了。
4. 属性装饰器
import 'reflect-metadata'
const log = console.log
function setClassName (name: string) {
return function <T extends { new (...args: any[]): {}}>(constructor: T) {
return class extends constructor {
+ constructor (...args: any[]) {
+ super(args)
+ const keys = Object.keys(this)
+ Reflect.getMetadataKeys(this).filter(key => keys.includes(key)).forEach(key => {
+ Object.defineProperty(this, key, Reflect.getMetadata(key, this))
+ })
+ }
__name = name
}
}
}
function descriptor (val: PropertyDescriptor) {
return function (target: any, propertyKey: string) {
Reflect.defineMetadata(propertyKey, val, target)
}
}
@setClassName('Teacher')
export default class Teacher {
@descriptor({ enumerable: false })
sex: string = '女'
}
const teacher = new Teacher()
log({ teacher, Teacher })
log(Object.keys(teacher))
为了上示例有一定的意义,这里结合了类装饰器。思路是这样的,先使用属性装饰器在对象上放一些metadata数据,类装饰器里的构造函数获取这些metadata数据,用这些数据做进一步处理。
参数
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
示例作用
用来设置属性的描述符。比如该示例log(Object.keys(teacher)),将打印出来[],一个空数组。
5. 参数装饰器
import 'reflect-metadata'
const log = console.log
function interceptor (before: Function, after: Function) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const fun = descriptor.value as Function
+ const matedata = Reflect.getMetadata(propertyKey, target)
descriptor.value = function (...args: any[]) {
+ if (matedata) {
+ args[matedata.parameterIndex] = args[matedata.parameterIndex].substring(0, matedata.format.length)
+ }
before.apply(this, args)
const res = fun.apply(this, args)
after.apply(this, [...args, res])
return res
}
}
}
function formatDate (format: string) {
return function (target: Object, propertyKey: string, parameterIndex: number) {
Reflect.defineMetadata(propertyKey, { parameterIndex, format }, target)
}
}
export default class Teacher {
salary: number = 10000
bonus: number = 2000
@interceptor((date: string) => log(`计算${date}薪资`), (date: string, res: number) => log('薪资支付完成:' + res))
pay (@formatDate('yyyy-MM') date: string) {
return this.salary + this.bonus
}
}
const teacher = new Teacher()
teacher.pay('2020-11-11')
参数装饰器结合了方法装饰器,可以看到,方法装饰器增加了4行代码,用来处理参数装饰器的数据。这里写的很简单,几乎没有实际作用,但是它具有参考价值,如果本篇文章写的太过复杂,反而掩盖主题思想。
参数
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 参数在函数参数列表中的索引。
示例作用
本来该示例是想做一个格式化参数的装饰器,这里简化了。当执行pay函数的时候,这次的输出,日期变成了'2020-11'
总结
以上就是typescript的五种装饰器,共同特点第一个参数是:对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。 也许有些地方看不太懂,这里还要强调一点儿:装饰是在运行时首先被执行的一段代码,也就是当node或者浏览器加载代码之后立即执行,先于所有的业务逻辑。上面的一些代码理解的时候,比如上面的方法装饰:拦截器,必须要明白装饰器是 运行时首先被执行的代码。
本篇文章主要是介绍装饰器的使用方式,以及简单的介绍要了一些使用场景,后续或许会模仿Nest.js写一个简单的服务端框架,看了之后或许会明白Nest.js的实现原理。
上篇文章挖了一个坑要写装饰器,这篇文章又挖了一个坑简单实现Nest.js核心,希望自己能够把这个坑填上。
全部代码
展开
import 'reflect-metadata'
const log = console.log
function setClassName (name: string) {
return function <T extends { new (...args: any[]): {}}>(constructor: T) {
return class extends constructor {
constructor (...args: any[]) {
super(args)
const keys = Object.keys(this)
Reflect.getMetadataKeys(this).filter(key => keys.includes(key)).forEach(key => {
Object.defineProperty(this, key, Reflect.getMetadata(key, this))
})
}
__name = name
}
}
}
function interceptor (before: Function, after: Function) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const fun = descriptor.value as Function
const matedata = Reflect.getMetadata(propertyKey, target)
descriptor.value = function (...args: any[]) {
if (matedata) {
args[matedata.parameterIndex] = args[matedata.parameterIndex].substring(0, matedata.format.length)
}
before.apply(this, args)
const res = fun.apply(this, args)
after.apply(this, [...args, res])
return res
}
}
}
function test (val: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
log(val, target, propertyKey, descriptor)
}
}
function descriptor (val: PropertyDescriptor) {
return function (target: any, propertyKey: string) {
Reflect.defineMetadata(propertyKey, val, target)
}
}
function formatDate (format: string) {
return function (target: Object, propertyKey: string, parameterIndex: number) {
Reflect.defineMetadata(propertyKey, { parameterIndex, format }, target)
}
}
@setClassName('Teacher')
export default class Teacher {
salary: number = 10000
bonus: number = 2000
private _age: number = 18
@descriptor({ enumerable: false })
sex: string = '女'
@interceptor((date: string) => log(`计算${date}薪资`), (date: string, res: number) => log('薪资支付完成:' + res))
pay (@formatDate('yyyy-MM') date: string) {
return this.salary + this.bonus
}
@test(false)
get age () {
return this._age
}
set age (val: number) {
this._age = val
}
}
const teacher = new Teacher()
log({ teacher, Teacher })
log(teacher['__name'])
teacher.pay('2020-11-11')
log(Object.keys(teacher))