代码来自后盾网

装饰器为 ts 提供了强大的代码复用功能

使用装饰器需要在 tsconfig.json 中修改

装饰器是试验性的功能,需要在配置文件中开启

tsconfig.json
1
2
"experimentalDecorators": true,
"emitDecoratorMetadata": true

类装饰器 ClassDecorator

相当于在原型对象上添加属性

类装饰器只有一个参数,是构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const moveDecorator: ClassDecorator = (target: Function) => {
console.log(target)
target.prototype.getPosition = (): { x: number; y: number } => {
return { x: 20, y: 20 }
}
}

@moveDecorator
class Player {}

@moveDecorator
class Tank {}

const player = new Player()
console.log(player.getPosition())

装饰器语法糖 @

不使用语法糖,需要传递类到对应的函数中。使用语法糖不需要手动调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const moveDecorator: ClassDecorator = (target: Function) => {
console.log(target)
target.prototype.getPosition = (): { x: number; y: number } => {
return { x: 20, y: 20 }
}
}

// 不使用语法糖
class Player {}
moveDecorator(Player)

// 使用语法糖
@moveDecorator
class Tank {}

const player = new Player()
console.log(player.getPosition())

装饰器叠加

可以使用多个装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const moveDecorator: ClassDecorator = (target: Function) => {
target.prototype.getPosition = () => {
console.log("获取位置")
}
}
const MusicDecorator: ClassDecorator = (target: Function) => {
target.prototype.playMusic = () => {
console.log("音乐播放")
}
}

@moveDecorator
class Player {}

const player = new Player()
player.getPosition()

@MusicDecorator
@moveDecorator
class Tank {}

const tank = new Tank()
tank.playMusic()
tank.getPosition()

实例:全局消息响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const MessageDecorator: ClassDecorator = (target: Function) => {
target.prototype.sendMessage = (message: string, type: "success" | "error" = "success") => {
return {
message,
type,
}
}
}

@MessageDecorator
class LoginController {
login() {
console.log("登录业务处理")
setTimeout(() => {
this.sendMessage("登录成功")
}, 2000)
}
}
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
const MusicDecorator = (type: string): ClassDecorator => {
switch (type) {
case "player":
return (target: Function) => {
target.prototype.playMusic = () => {
console.log("play player music")
}
}
case "tank":
return (target: Function) => {
target.prototype.playMusic = () => {
console.log("play tank music")
}
}
default:
return (target: Function) => {
target.prototype.playMusic = () => {
console.log("play other music")
}
}
}
}

@MusicDecorator("player")
class Player {}
new Player().playMusic()

@MusicDecorator("tank")
class Tank {}
new Tank().playMusic()

方法装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const ShowDecorator: MethodDecorator = (...args: any[]) => {
console.log(args)
/**
打印参数:
[0]: 如果是静态函数就是构造函数,如果是普通方法就是其原型对象
[1]: 使用装饰器的方法名称
[2]: 方法属性的描述
[
User: {},
"show",
{
"value": [Function (anonymous)] // 函数的内容
"writable": true,
"enumerable": false,
"configurable": true
}
]
*/
}
class User {
@ShowDecorator
public show() {}
}

可以使用函数装饰器更改函数的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const ShowDecorator: MethodDecorator = (...args: any[]) => {
console.log(args)
args[2].value = () => {
console.log("ShowDecorator changed")
}
}
class User {
@ShowDecorator
public show() {
console.log("show function ")
}
}

new User().show() // 打印 "ShowDecorator changed",函数内容已经被更改

上面那样使用数组接收后调用不是很直观,我们可以使用变量名接收方法装饰器参数

const ShowDecorator: MethodDecorator = (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => { ... };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const ShowDecorator: MethodDecorator = (
target: Object,
propertyKey: string | symbol,
descriptor: PropertyDescriptor
) => {
descriptor.value = () => {
console.log("ShowDecorator changed")
}
}
class User {
@ShowDecorator
public show() {
console.log("show function ")
}
}

new User().show() // 打印 "ShowDecorator changed",函数内容已经被更改

调用装饰器时,也可以更改静态方法的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const ShowDecorator: MethodDecorator = (
target: Object,
propertyKey: string | symbol,
descriptor: PropertyDescriptor
) => {
descriptor.value = () => {
console.log("ShowDecorator changed")
}
}
class User {
@ShowDecorator
public static show() {
console.log("show function ")
}
}

User.show() // 打印 "ShowDecorator changed",函数内容已经被更改

调用 writable = true 控制方法不能重新声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const ShowDecorator: MethodDecorator = (
target: Object,
propertyKey: string | symbol,
descriptor: PropertyDescriptor
) => {
descriptor.value = () => {
console.log("ShowDecorator changed")
}
descriptor.writable = false
}
class User {
@ShowDecorator
public static show() {
console.log("show function ")
}
}

User.show() // 打印 "ShowDecorator changed",函数内容已经被更改
User.show = () => {
console.log("重写show方法")
}
User.show()

示例:模拟代码高亮

可以先使用变量将函数内容保存起来,在自定义操作后直接调用保存的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const HighlightDecorator: MethodDecorator = (
target: Object,
propertyKey: string | symbol,
descriptor: PropertyDescriptor
) => {
const method = descriptor.value
descriptor.value = () => {
return `<div style="background: red;">${method()}</div>`
}
}
class User {
@HighlightDecorator
public show() {
return "js code "
}
}
new User().show()

示例:结合装饰器工厂实现延迟执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const SleepDecorator = (times: number = 2000): MethodDecorator => {
return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
const method = descriptor.value
descriptor.value = () => {
setTimeout(() => {
method()
}, times)
}
}
}
class User {
@SleepDecorator(3000)
public show() {
console.log("wxw")
}
}
new User().show()

示例:全局异常处理

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 ErrorDecorator: MethodDecorator = (
target: Object,
propertyKey: string | symbol,
descriptor: PropertyDescriptor
) => {
const method = descriptor.value

descriptor.value = () => {
try {
method()
} catch (e) {
// 函数中抛出的错误在这里进行处理
console.log("%c异常处理", "color: green;")
console.log(`%c${e}`, "color: red")
}
}
}

class User {
@ErrorDecorator
find() {
throw new Error("出错了")
}
}
new User().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
26
27
28
29
30
31
type UserType = { name: string; isLogin: boolean; permissions: string }
const user = {
name: "wxw",
isLogin: true,
permissions: ["admin", "member"],
}
const AccessDecorator = (keys: string[]): MethodDecorator => {
return (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => {
const method = descriptor.value
const validate = () => keys.every((k) => user.permissions.includes(k))
descriptor.value = () => {
// 控制用户登录并且有权限才可访问
if (user.isLogin && validate() === true) {
console.log("验证通过")
method()
return
} else {
console.log("验证失败")
return false
}
}
}
}

class User {
@AccessDecorator(["admin"])
find() {
console.log("find function")
}
}
new User().find()

属性装饰器

接收的参数

  • args[0]: target 静态参数就是构造函数,普通参数就是其原型对象
  • args[1]: propertyKey 属性名称
  • args[2]: undefined
1
2
3
4
5
6
const PropDecorator: PropertyDecorator = (target: Object, propertyKey: string | symbol) => {
console.log(target, propertyKey)
}
class User {
public username: string
}

实例:使用属性装饰器动态改变对象属性

将属性改为全部大写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const UpperDecorator: PropertyDecorator = (target: Object, propertyKey: string | symbol) => {
let value: string = undefined

//在这里使用Object.defineProperty对原数据进行更改
Object.defineProperty(target, propertyKey, {
get() {
return value.toUpperCase()
},
set(v) {
value = v
},
})
}

class Article {
@UpperDecorator
public title: string | undefined
}

const article = new Article()
article.title = "article title"
console.log(article.title)

参数装饰器

接收的参数

  • args[0]: target 静态参数就是构造函数,普通参数就是其原型对象
  • args[1]: propertyKey 属性名称
  • args[2]: parameterIndex 参数所在的位置,从 0 开始计数
1
2
3
4
5
6
const ParamDecorator: ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => {
console.log(target, propertyKey, parameterIndex)
}
class User {
show(id: number, @ParamDecorator user: { name: string }) {}
}
装饰器执行顺序:

参数装饰器 -> 方法装饰器