TypeScript中的装饰器语法

装饰器语法虽然还处于提案阶段,但是确实能够在实际应用中方便的解决一些问题,近期看了一下TypeScript中装饰器的一些基本用法,做了一些笔记如下.

类装饰器

装饰器配置

装饰器是一个函数

装饰器属于一个新特性,需要在tsconfig.json中进行配置才能正常使用:

1
2
3
// 需要将如下两个配置项设置为true才能启用装饰器
experimentalDecorators: true,
emitDecoratorMetadata: true

装饰器的种类有很多,比如类装饰器ClassDecorator、方法装饰器MethodDecorator、属性装饰器PropertyDecorator,参数装饰器ParameterDecorator等

装饰器的定义

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
// ClassDecorator表明类型是 类的装饰器
// 装饰器接受一个参数,就是要装饰的内容,编译后拿到的是类的构造函数,所以此处参数类型定义为函数
// toString.call(Tank) 类的类型就是Function
// '[object Function]'
const moveDecorator: ClassDecorator = function(target: Function){
console.log(target)
// 在装饰内容的原型上添加getPosition方法
target.prototype.getPosition = ():{x:number, y: number} => {
return {x: 100, y: 200}
}
}
// 在class Tank前添加装饰器,就代表装饰的是Tank这个类,装饰器中接收的target就是这个类
@moveDecorator
class Tank {
// 声明该方法,不然实例无法调用
public getPosition(){}
}
const t = new Tank()
t.getPosition()

@moveDecorator
class Player {
public getPosition(){}
}
const p = new Player()
p.getPosition()

装饰器的@decorator是一个语法糖,下边的两种方法得到的结果是等价的

只是语法糖看起来更加明了,知道修饰的是什么,而不用去查找函数的调用

这是类装饰器的原理,其他装饰器不是的,比如方法装饰器是Object.defineProperty()

1
2
3
4
5
6
function decorator(target: object){}
// 使用语法糖
@decorator
class Tank{}
// 不使用语法糖
decorator(Tanke)

装饰器叠加

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
// ClassDecorator表明类型是 类的装饰器
// 装饰器接受一个参数,就是要装饰的内容,编译后拿到的是类的构造函数,所以此处参数类型定义为函数
const moveDecorator: ClassDecorator = function(target: Function){
console.log(target)
// 在装饰内容的原型上添加getPosition方法
target.prototype.getPosition = ():{x:number, y: number} => {
return {x: 100, y: 200}
}
}

const musicDecorator: ClassDecorator = function(target: Function){
target.prototype.playMusic = (): void => {
console.log('播放音乐')
}
}
// 在class Tank前添加装饰器,就代表装饰的是Tank这个类,装饰器中接收的target就是这个类
// 可以叠加多个装饰器
@moveDecorator
@musicDecorator
class Tank {
// 声明方法,不然实例无法调用
public getPosition(){}
public playMusic(){}
}
const t = new Tank()
console.log(t.getPosition())
t.playMusic()
// { x: 100, y: 200 }
// 播放音乐

装饰器实现统一的消息响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 统一的消息响应装饰器
const MessageDecorator = function(target: Function){
target.prototype.message = (content: string) => {
console.log(content)
}
}

@MessageDecorator
class LoginController {
public message(content:string){}
public login(){
console.log('开始登录业务处理')
this.message('登录成功啦')
}
}
new LoginController().login()

装饰器工厂函数

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
// 装饰器的工厂函数,用于生成装饰器
const musicDecoratorFactory = (type: string): ClassDecorator => {
switch(type){
case 'Tank':
return (target: Function) => {
target.prototype.playMusic = (): void => {
console.log('坦克播放音乐')
}
}
break;
case 'Player':
return (target: Function) => {
target.prototype.playGame = (): void => {
console.log('玩家玩游戏')
}
}
break;
default:
return (target: Function) => {
target.prototype.playMusic = (): void => {
console.log('默认播放音乐')
}
}
}

}

@musicDecoratorFactory('Tank')
class Tank {
playMusic(){}
}
const T = new Tank()
T.playMusic()

@musicDecoratorFactory('Player')
class Player {
playGame(){}
}
const P = new Player()
P.playGame()

方法装饰器

装饰器只能用来装饰类和类的方法,因为函数存在函数提升的问题,所以不能用来装饰普通函数

修饰普通方法则target拿到原型对象

修饰静态方法则target拿到构造函数

方法装饰器是通过Object.defineProperty()实现的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 方法装饰器
const showDecorator: MethodDecorator = (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
// 第一个是修饰的函数,根据修改的函数的类型不同会有不同的结果
// 第二个是函数名
// 第三个是defineProperty的属性,value,writable,enumerable,configurable
// value就是修饰的函数的内容
// 如果ts目标版本低于es5,则descriptor为undefind
console.log(target, propertyKey, descriptor)
}

class User {
@showDecorator
public show(){
console.log('hhhh')
}
}

静态方法

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
const Decorator: MethodDecorator = function(target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor){
console.log(target)
console.log(propertyKey)
console.log(descriptor)
// 打印结果:
// [Function: User] { show: [Function (anonymous)] }
// show
// {
// value: [Function (anonymous)],
// writable: true,
// enumerable: true,
// configurable: true
// }
// 设置为只读
descriptor.writable = false
}
class User {
// 静态方法装饰器
@Decorator
public static show(){
console.log('hhh')
}
}

User.show()

对方法的返回内容进行修饰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const highlightDecorator: MethodDecorator = function(target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor){
const method = descriptor.value
descriptor.value = () => {
return `<span style="color:red;">${method()}</span>`
}
}
class User {
// 静态方法装饰器
@highlightDecorator
public response(){
return 'hhh.xxx.yyy'
}
}
console.log(new User().response())

处理抛出错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const ErrorDecorator: MethodDecorator = (target: object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
const method = descriptor.value
descriptor.value = () => {
try{
method()
}catch(error){
console.log(`我抛出了一个错误,错误内容是${error}`)
}
}
}
class People {
@ErrorDecorator
public find(){
throw new Error('error')
}
}

new People().find()

属性装饰器与参数装饰器

属性装饰器根据修饰的属性是普通属性还是静态属性,分别能得到原型对象和构造函数

参数装饰器根据参数所在的方法是普通方法和静态方法,分别能得到方法的原型对象和构造函数

参数装饰器会比方法装饰器更先一步执行

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
// 属性装饰器
const propDecorator: PropertyDecorator = (target: Object, propertyKey: string | symbol) => {
// target如果是普通属性得到的是原型对象,如果是静态属性得到的是构造函数
console.log(target)
// {} [Function: HD]
console.log(propertyKey) // 属性名称
// name age
}
// 参数装饰器
const paramsDecorator: ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => {
// 如果参数所在的方法是普通方法则得到原型对象 { say: [Function (anonymous)] }
// 如果参数所在的方法是静态方法则得到构造函数 [Function: HD] { say: [Function (anonymous)] }
console.log(target)
console.log(propertyKey) // 参数所在的方法名 say
console.log(parameterIndex) // 参数的下标 1
}
class HD {
@propDecorator
public name: string | undefined
@propDecorator
public static age: number

public say(id: number, @paramsDecorator content: string){}
// public static say(id: number, @paramsDecorator content: string){}
}

属性装饰器动态转换对象属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 使用属性装饰器将属性值转换为小写
const propDecorator: PropertyDecorator = (target: Object, propertyKey: string | symbol) => {
let value: string
// 使用访问器对属性进行操作,使其返回小写
Object.defineProperty(target, propertyKey, {
get: () => {
return value.toLowerCase()
},
set: (v) => {
value = v
}
})
}

class HD {
@propDecorator
public title: string | undefined
}

const obj = new HD()
obj.title = "HELLO WORLD"
console.log(obj.title) // hello world

元数据

元数据用于给对象的属性添加额外的描述信息

ts默认不支持元数据,需要引入reflect-metadata这个npm包才行

Reflect.defindMetadata用于设置元数据,接收四个参数:

​ [元数据属性名, 元数据属性值, 要设置的对象, 要设置的对象的属性]

Reflect.getMetadata用于查询元数据,接收三个参数:

​ [元数据的属性名, 对象名, 对象的属性名]

1
2
3
4
5
6
7
8
import 'reflect-metadata'
const hd = {
name: 'alin'
}
// 设置元数据
Reflect.defineMetadata('desc',{title:'hhh'}, hd, 'name')
// 读取元数据
console.log(Reflect.getMetadata('desc', hd, 'name'))

对参数做必传校验

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
import 'reflect-metadata'
// 属性装饰器
const requiredDecorator: ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => {
let requiredParams: number[] = []
requiredParams.push(parameterIndex)
// 将需要必传的参数的下标存入元数据
Reflect.defineMetadata('required', requiredParams, target, propertyKey)
}

const validateDecorator: MethodDecorator = (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
const method = descriptor.value
descriptor.value = function(){
// 拿到需要必传的参数的下标
const requiredParams: number[] = Reflect.getMetadata('required', target, propertyKey) || []
// 如果必传参数没有传递则抛出错误
requiredParams.forEach( function(index){
if(index > arguments.length || arguments[index] === undefined){
throw new Error('请传递必要的参数')
}
})
// 验证通过则执行原来的方法
return method.apply(this, arguments)
}
}

class HD {
@validateDecorator
public find(name: string, @requiredDecorator id: number){
console.log(id)
}
}
作者

胡兆磊

发布于

2022-03-23

更新于

2023-01-16

许可协议