代码来自后盾网
装饰器为 ts 提供了强大的代码复用功能
使用装饰器需要在 tsconfig.json 中修改
装饰器是试验性的功能,需要在配置文件中开启
{ "experimentalDecorators": true, "emitDecoratorMetadata": true, }
类装饰器 ClassDecorator
相当于在原型对象上添加属性
类装饰器只有一个参数,是构造函数。
const moveDecorator: ClassDecorator = (target: () => void) => { 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())
装饰器语法糖 @
不使用语法糖,需要传递类到对应的函数中。使用语法糖不需要手动调用。
const moveDecorator: ClassDecorator = (target: () => void) => { 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())
装饰器叠加
可以使用多个装饰器
const moveDecorator: ClassDecorator = (target: () => void) => { target.prototype.getPosition = () => { console.log("获取位置") } } const MusicDecorator: ClassDecorator = (target: () => void) => { 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()
实例:全局消息响应
const MessageDecorator: ClassDecorator = (target: () => void) => { 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()
装饰器工厂
根据不同的条件返回不同的装饰器
function MusicDecorator(type: string): ClassDecorator { switch (type) { case "player": return (target: () => void) => { target.prototype.playMusic = () => { console.log("play player music") } } case "tank": return (target: () => void) => { target.prototype.playMusic = () => { console.log("play tank music") } } default: return (target: () => void) => { target.prototype.playMusic = () => { console.log("play other music") } } } } @MusicDecorator("player") class Player {} new Player().playMusic() @MusicDecorator("tank") class Tank {} new Tank().playMusic()
方法装饰器
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() {} }
可以使用函数装饰器更改函数的内容
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) => { ... };
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",函数内容已经被更改
调用装饰器时,也可以更改静态方法的内容
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
控制方法不能重新声明
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()
示例:模拟代码高亮
可以先使用变量将函数内容保存起来,在自定义操作后直接调用保存的函数
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()
示例:结合装饰器工厂实现延迟执行
function 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()
示例:全局异常处理
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()
示例:根据权限限制访问
可以根据传入的权限数组进行判断用户是否有访问权限
interface UserType { name: string, isLogin: boolean, permissions: string } const user = { name: "wxw", isLogin: true, permissions: ["admin", "member"], } function 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() } else { console.log("验证失败") return false } } } } class User { @AccessDecorator(["admin"]) find() { console.log("find function") } } new User().find()
属性装饰器
接收的参数
- args0:
target
静态参数就是构造函数,普通参数就是其原型对象 - args1:
propertyKey
属性名称 - args2: undefined
const PropDecorator: PropertyDecorator = (target: object, propertyKey: string | symbol) => { console.log(target, propertyKey) } class User { public username: string }
实例:使用属性装饰器动态改变对象属性
将属性改为全部大写
const UpperDecorator: PropertyDecorator = (target: object, propertyKey: string | symbol) => { let value: string // 在这里使用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)
参数装饰器
接收的参数
- args0:
target
静态参数就是构造函数,普通参数就是其原型对象 - args1:
propertyKey
属性名称 - args2:
parameterIndex
参数所在的位置,从 0 开始计数
const ParamDecorator: ParameterDecorator = (target: object, propertyKey: string | symbol, parameterIndex: number) => { console.log(target, propertyKey, parameterIndex) } class User { show(id: number, @ParamDecorator user: { name: string }) {} }
装饰器执行顺序:
参数装饰器 -> 方法装饰器