目前 js 支持装饰器。但还未进入标准。是一种面向对象的编程方式
解决什么问题
- 分离关注点。例如:对用户对象中的数据进行验证
- 重复代码问题
- 能够带来额外的信息量,看可以为某些属性、类、参数、方法提供元数据信息。这样在运行时我们可以拿到这些元数据进行我们想要的操作
上述前两个问题产生的根源:某些信息,在定义时,能够附加的信息量有限
装饰器的本质
在 js 中,装饰器是一个函数,因为是 js 的东西,所以会参与运行。
类装饰器
类装饰器本质是一个函数,该函数接受一个参数,表示类本省(构造函数本省)。
- 因为装饰器还不是标准,在 ts 中使用时会报错,需要开启 experimentalDecorators
- 装饰器函数的运行时间:在类定义后直接运行
- 类装饰器的返回值只有两种:void(仅运行函数)、返回一个新的类(会将新的类替换掉装饰目标)
//在 ts 中,该函数必须接收一个参数,代表装饰的类,且需要约束构造函数函数的参数
function test(target: new(...args: any[]) => object){
}
@test
class A {
constructor(public a:string){}
}
// 给装饰器传参
// 执行装饰器,并且返回值需要是一个装饰器
function test(a:string) {
return function (target: new(...args: any[]) => object){
}
}
@test('fdsfds')
class A {
constructor(public a:string){}
}
// 多个装饰器的情况
// 装饰器从下往上执行
// 如果是给装饰器传参的情况,那么先执行函数,然后返回的装饰器才按照从下往上的顺序执行
type constructor = new (...args:any[]) => object
function d1(target:constructor){}
function d2(target:constructor){}
// function d1(){ return funciton (target:constructor){} }
// function d2(){ return funciton (target:constructor){} }
@d2
@d1
// @d2() 先执行 d2 然后返回装饰器
// @d1() 先执行 d1 然后返回装饰器
// 返回的装饰器在类定义完后,按照从下往上也就是 d1、d2 的顺序执行
class A {}
成员装饰器
属性装饰器
属性装饰器也是一个函数,该函数接收两个参数
- 第一个参数:如果是静态属性,则为类本身;如果是实例,则为类的原型
- 第二个参数:固定位一个字符串,表示属性名
function d(target:any, key:string){}
class A {
@d
prop1:string
@d
static prop2:string
}
方法装饰器
方法装饰器也是一个函数,该函数接收三个参数
- 第一个参数:如果是静态方法,则为类本身;如果是实例方法,则为类的原型
- 第二个参数:固定为一个字符串,表示方法名
- 第三个参数:描述符对象(可以用 Object.defineProperty 定义的那个描述符)
function m() {
return functioin (target: any, key: string, descriptor: PropertyDescriptor){}
}
class A {
@m()
method1() {}
}
装饰器补充
常用库:
- reflect-metadata
- 由于我们在使用装饰器时,需要存储一些元信息,通常我们可以挂载在原型上,但是这样会造成原型污染。所以我们应该将其存储在外部。reflect-metadata 就为我们提供了这样的外部元信息存储的能力 (类、属性等的元数据存储)。
- class-validator
- 用来做数据校验,提供了各种校验装饰器,例如 @Length、@Max 等。ts 在运行时没有类型检查,我们可以借助该库,做一些运行时的校验
- class-transformer
- 用来把平面对象转换为指定的类的实例对象。通常用于在服务端做类型校验,将接受到的数据进行转换为对象,然后结合 class-validator 对数据进行校验。例如:
// 服务端定义的 User 类型
class User {
name: string
age: number
height: number
}
// 前端发送过来的数据
ctx.request.body = {
name: 'fdsfd',
age: '123',
height: 178
}
const user = plainToClass(ctx.request.body, User)
参数装饰器:依赖注入、依赖倒置(用到很少,大型后端项目)
要求有三个参数:
- 第一个参数:如果方法是静态的,则为类本省,如果方法是实例方法,则为类的原型
- 参数名称
- 在参数列表中的索引
function test(target: any, method: string, index:number){}
class A {
sum(a: number, @test b:number){}
}
关于 TS 自动注入元数据:如果安装了 reflect-metadata
,并且导入了该库,并且在某个成员上添加了元数据,并且启用了 emitDecoratorMetadata
,则在 TS 编译结果中,会将类型约束,作为元数据加入到相应位置,这样一来,TS 的类型检查将有机会在运行时进行