阅读前须知: 需要了解typescript的基本操作, tsconfig的配置。 装饰器experimental的版本非常类似于Object.defineProperty(target, prop, descriptor),需要了解。
typescript装饰器在5.0版本不用experimental了,可以直接用。还需要注意如果用的ts 5的话,把这个配置注释掉,要不然还是跑的老的experimental的规则。
重要Note:
This new decorators proposal is not compatible with
--emitDecoratorMetadata, and it does not allow decorating parameters. Future ECMAScript proposals may be able to help bridge that gap.
和老的experimental以及emitDecoratorMetadata不兼容,还不能在函数参数上加。
用下面代码结合ts playground切换不同版本看一下decorator函数都接收了什么参数:
function ComponentDeco(target:Function){
console.log("component", target)
}
//for 5.0.+ use
function ComponentDeco(...args:any){
//target is the constructor of MyClass
console.log("component", args)// 5+ 接收两个参数
}
function PropertyDeco(...args:any){
console.log("property",args)
}
function MethodDeco(...args:any){
console.log("method",args)
}
@ComponentDeco
class MyClass{
@PropertyDeco
name='hao'
@MethodDeco
SayHello(){
console.log('Hello from inside class SayHello')
}
}
4.9.5版本 + 开启experimentalDecorators 配置
输出
可以看到methodDeco第一个参数:函数本身,第二个参数:成员名字,第三个参数:这个成员的属性描述符
5.0.x版本。不用再设置experimentalDecorators了,正式在ts中可用。 装饰器接受的参数有比较大的改动。
不能修饰类外部的普通函数哦
www.typescriptlang.org/docs/handbo…
专注看一下类method装饰器的接收的参数,
function MethodDeco(...args:any){
console.log("method",args)
}
class MyClass{
name='hao'
@MethodDeco
SayHello(hello1:string, hello2:string){
console.log('Hello from inside class SayHello',hello1,hello2,this.name)
}
}
输出
第一个参数接收函数本身,第二个参数接收一个 ClassMethodDecoratorContext 类型的对象,这个对象里含有对这个method的各种描述。在5以前的版本,method接收三个参数,这里如果定义装饰器函数用了三个参数,就会报错了。
通常装饰器定义和用起来要:
function deco(toBeDecoedFunc):
function DecoedFunc(args):
//do something here
toBeDecoedFunc(args)
return DecoedFunc
@deco
function IwantToBeDecoedToDoSthElse(arg1){
...
}
从装饰器内部,会返回一个内部函数DecoedFunc,原函数IwantToBeDecoedToDoSthElse会被替换成它来执行。可以联想一下复合函数deco(origin(arg)),但是换了个写法。当然也可以这个DecoedFunc不调用原来的方法,做一些特殊的事情。
接下来,写一个简单的ts 5的装饰器,然后tsc编译一下看看执行过程:
function MethodDecoReturnFuncToCall(target:any, context:any){
function decoedMethod(this:any,args:any){
console.log('-----',this)
target.call(this, args)
}
return decoedMethod
}
class MyClass2{
name='hao'
@MethodDecoReturnFuncToCall
SayHello(hello1:string, hello2:string){
console.log('Hello from inside class SayHello',hello1,hello2,this.name)
}
}
(new MyClass2).SayHello('1','2')
经过MethodDecoReturnFuncToCall装饰后,调用SayHello的时候,就不再是调用SayHello了,实际上全权委托给了返回的decoedMethod(就是复合函数),它的this也变成了MyClass2,它也接收了SayHello调用时传入的参数'1'和'2'。
怎么回事呢,从tsc编译的结果(target:ES6)分析一下吧,只分析关键的几行:
-
var MyClass2 = function ()这里 最后返回_a, 它的值是由后面的逗号表达式决定的,1:定义类,2: 设置decorator,具体是通过__esDecorate这个函数调用来实现的,传入decorator数组,我们这里就有一个所以就是往__esDecorate传入数组
[MethodDecoReturnFuncToCall] -
__esDecorate函数里,最关键的var __esDecorate = ... { function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } //装饰器函数返回值不是undefined和function类型就会报错,所以装饰器是能不返回值的,, var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); //这一行把MethodDecoReturnFuncToCall执行了一下,它返回了decoedMethod作为result ... ... else if (_ = accept(result)) { //注意装饰器函数不返回值的时候,descriptor就不会被改变,保持原样,这种时候还是调用了SayHello if (kind === "field") initializers.unshift(_); else descriptor[key] = _; //这里key='value' //_ = decoedMethod } if (target) Object.defineProperty(target, contextIn.name, descriptor); //最关键, // 将target(MyClass2),新增一个属性,contextIn.name (= Sayhello) // descriptor里的value是decoedMethod, // 所以就将MyClass2的Sayhello方法替换成了decoedMethod, // 注意这些都是在定义MyClass2的时候发生的,所以定义时,装饰器函数MethodDecoReturnFuncToCall就会被执行一次 }
由编译后的代码可见,类在定义时,会调用一下装饰器函数,并使用defineProperty,将被装饰的类方法替换成装饰器函数的返回函数。但装饰器函数不返回函数,且不返回undefined的话,调用这个被装饰的类方法就会报错了.但是如果装饰器函数返回类型undefined,那么调用这个类方法还是执行的原来的方法,不会被装饰器函数替换。
试验一下:
function MethodDecoReturnFuncToCall(target:any, context:any){
return undefined
}
class MyClass2{
name:string
constructor(name:string){
this.name=name
}
@MethodDecoReturnFuncToCall
SayHello(hello1:string, hello2:string){
console.log('Hello from inside class SayHello',hello1,hello2,this.name)
}
}
(new MyClass2('haohao')).SayHello('1','2')
//打印结果
// Hello from inside class SayHello 1 2 haohao
function MethodDecoReturnFuncToCall(target:any, context:any){
return function(this:any, args:any){
console.log('Hello from deco, not from SayHello method '+ this.name)
}
}
//print
//Hello from deco, not from SayHello method haohao
ts 5 typed decorator
官方给的例子 Well-typed decorators
function loggedMethod<This, Args extends any[], Return>(
target: (this: This, ...args: Args) => Return,
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
) {
const methodName = String(context.name);
function replacementMethod(this: This, ...args: Args): Return {
console.log(`LOG: Entering method '${methodName}'.`)
const result = target.call(this, ...args);
console.log(`LOG: Exiting method '${methodName}'.`)
return result;
}
return replacementMethod;
}
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
@loggedMethod
greet() {
console.log(`Hello, my name is ${this.name}.`);
}
}
const p = new Person("Ray");
p.greet();
function loggedMethod<This, Args extends any[], Return>(
target: (this: This, ...args: Args) => Return,
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return
函数签名后的 <This, Args extends any[], Return> 定义本函数使用的三个泛型
第一个参数 (this: This, ...args: Args) => Return 一个函数类型(T, G)=> Y,
ClassMethodDecoratorContext 类型,
可以简化一下写法,把那个函数类型用个单独的类型替代一下,增加一个泛型Fn
function loggedMethod<This, Args extends any[], Return, Fn extends (this: This, ...args: Args) => Return>(
target: Fn,
context: ClassMethodDecoratorContext<This, Fn>
)
using decorator factory
在装饰器函数外面再套一层函数,接收一些自定义的参数,来定制decorator
function lengthlimit(count:number){
return function<This, Args extends [Array<any>, ...any[]], Return, Fn extends (this:This, ...args:Args)=>Return>(
target:Fn,
context: ClassMethodDecoratorContext<This, Fn>
){
function replacementMethod(this:This, ...args:Args):Return {
const firstArg = args[0] //Array<any>
if(firstArg.length>count){
throw new Error(`cannot call with >${count} items`)
}
return target.call(this, ...args)
}
return replacementMethod
}
}
class TestClass{
@lengthlimit(2)
callWithNumLimit(arr:any[], info:string){
return `${info}: ${arr.reduce((accrslt,currentvalue)=>accrslt+currentvalue,0)}` as unknown
}
}
console.log((new TestClass).callWithNumLimit([1,2],'calling:'))
ts编译成js后,_callWithNumLimit_decorators = [limit(2)]; 来取出动态制造出的装饰器函数
使用ts 5的 field decorator
#need working on this .... pending on 0722
//ts5: for field decorator, target ===undefined
function Required(target: undefined, context: ClassFieldDecoratorContext) {
return function(firstName:string){//这里就是接受的修饰的firstName属性
return "decoed:"+firstName//覆盖掉,new User的时候firstName就会变成deoded
}
}
//注意不要再自己写contruct了,会把装饰器返回的覆盖掉
class User {
@Required
firstName: string;
}
const user1 = new User();
//tbd
@withEmploymentDate
//@withEmploymentDateOnPrototype
class Manager {
task: string = 'Simple task'
project: string = 'Simple project'
constructor(task:string){
this.task=task
console.log('Initializing the Manager class')
}
}
console.log(new Manager('manager task'))
// function withEmploymentDateOnPrototype(arg: Function) {
// arg.prototype.employmentDateOnPrototype = new Date().toISOString();
// }
//用原来的class extend出一个新的class返回回来,来实现调用构造函数
function withEmploymentDate<T extends { new(...args: any[]): {} }>(baseClass: T) {
console.log('Invoking decorator!!!')
return class extends baseClass {
//employmentDate = new Date().toISOString();
constructor(...args: any[]) {
super(...args);
console.log('Adding employment date to ' + baseClass.name)
}
}
}