浅谈 typescript 装饰器
typescript 的装饰器,是个很尴尬的存在,从这个概念被提出以来,就一直在经历各种规范上的修改,直到现在还没有一种公认、稳定的规范拿上台。
装饰器是和 class 的概念相关的,从尤大的话来说,前端页面的交互逻辑使用 class 来写,可能并不是那么顺手。从 vue3 放出的 api 来看,vue3 最终还是选择了开放函数式编程的接口。用尤大的话来讲:设计一个 class 编程接口,要踩的坑实在是太多。
不过即使是冷门的语法,也阻止不了 好奇的我去 科研一下。
什么是装饰器
说到装饰器,我们首先联想到的是 css。在编写 html 文档时,我们使用 css 写好页面的样式,然后通过 css 选择器将这些样式绑定到 html 标签。
typescript 的装饰器也是类似的使用方法,编写一个装饰器函数,然后通过 @methodName 将这个函数绑定到类、类的属性、类的方法。
它们的使用方法,有点像是做好一朵小红花,然后别在某个小朋友的衣领上一样,顾名思义装饰器。
类属性装饰器
typescript 的属性装饰器,是指对类中的属性进行装饰,以代码入侵性较低的方式增强该属性。当我们使用类对象的属性时,完成我们自定义的额外功能。
下面是一个用 es5 风格编写的属性装饰器的例子,通过 Object.defineProperty 将类的普通属性改写成存取器属性,然后通过 Object accessor 拦截属性的基础操作,从而添加自定义的额外代码。
属性装饰器函数有固定的参数:target 指被修饰的类,key 指被修饰类的属性。
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
| function logProperty(target:any,key:string){ delete target[key] let backing = '_'+key
Object.defineProperty(target,backing,{ writable:true, configurable:true, enumerable:true })
function getter(this:any){ const value = this.backing console.log(`Get: ${key} => ${value}`) return value }
function setter(this:any,newVal:any){ console.log(`Set: ${key} => ${newVal}`) this.backing = newVal }
Object.defineProperty(target,key,{ get:getter, set:setter, configurable:true, enumerable:true }) }
class Employee{ @logProperty name?:string }
let e = new Employee e.name e.name = 'sugar' e.name
|
我们使用 es6 风格的 Proxy 和 Reflect 重写一下看看,哈哈,我也不会写,毕竟我也是抄别人的。
类装饰器
类装饰器可以给一个类插入一个类方法。
我们创建一个什么都不写的空类:Greeter,然后使用类装饰器为这个空类添加一个方法:greet()
类装饰器只需要一个参数:target,指代被装饰的类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function Greeting(target:any){ target.prototype.greet = function(){ console.log(`我是greet方法哒,没想到吧 !!`) } } }
@Greeting class Greeter{
}
let greeter = new Greeter() greeter.greet()
|
如果我们的装饰方法需要传递参数,那么可以使用下面的写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function Greeting(greeting:string){ return function(target:any){ target.prototype.greet = function(){ console.log(`我是greet方法哒,没想到吧! ${greeting} !!`) } } }
@Greeting('乾杯 - ( ゜- ゜)つロ') class Greeter{
}
let greeter = new Greeter() greeter.greet()
|
类方法装饰器
方法装饰器和属性装饰器相比,多了一个参数:descriptor,这个参数里记录了原函数的引用,用于调用原函数。
下面的例子中,我们通过改写 descriptor.value 将原来的方法替换成了新方法,并新增了日志功能。
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 41 42 43 44 45 46
| function LogMethod(target:any,key:string,descriptor:any){ const oldMethod = descriptor.value const newMethod = function(this:any,...args:any[]){ const result = oldMethod.call(this,args) if(!this.logOutput){ this.logOutput = new Array<any>() } this.logOutput.push({ method:key, parameter:args, result:result, timestamp:new Date() }) } descriptor.value = newMethod }
class Calculator { @LogMethod double(num:number){ return num * 2 } }
let c = new Calculator() c.double(2) c.double(10) console.log(c.logOutput)
|
结语
以上是3种常用装饰器的使用案例,typescript 还有其他装饰器,这里就不列出了,因为笔者也只了解这3种。
本篇没有对装饰器做出足够详细的讲解,笔者也只懂这些,不足之处请多多包涵。