DecoratorDecorator并不是什么新鲜事。在TypeScript1.5+版本中,我们可以使用内置类型ClassDecorator、PropertyDecorator、MethodDecorator和ParameterDecorator来更快的编写Decorator,比如MethodDecorator:declaretypeMethodDecorator=(target:Object,propertyKey:string|symbol,descriptor:TypedPropertyDescriptor)=>TypedPropertyDescriptor|空白;使用时,只需在相应的地方加上类型注解,匿名函数的参数类型就会自动推导出来。functionmethodDecorator():MethodDecorator{return(target,key,descriptor)=>{//...};}值得一提的是,如果在Decorator中给目标类的原型添加属性,TypeScript并不知道这些:functiontestAble():ClassDecorator{returntarget=>{target.prototype.someValue=true}}@testAble()classSomeClass{}constsomeClass=newSomeClass()someClass.someValue()//错误:属性'someValue'在类型'SomeClass'上不存在。这是很常见的,尤其是当你想用装饰器扩展一个类时。GitHub上有一个关于这个issue的issue,目前还没有合适的方案来实现。主要问题是TypeScript不知道目标类是否使用了装饰器,以及装饰器的名称。从这个问题来看,建议的解决方案是使用Mixin:typeConstructor=new(...args:any[])=>T//mixin函数的声明,也需要实现declarefunctionmixin(...MixIns:[Constructor,Constructor]):Constructor;classMixInClass1{mixinMethod1(){}}classMixInClass2{mixinMethod2(){}}classBaseextendsmixin复制代码(MixInClass1,MixInClass2){baseMethod(){}}constx=newBase();x.baseMethod();//OKx.mixinMethod1();//OKx.mixinMethod2();//OKx.mixinMethod3();//Error在将大量的JavaScriptDecorators重构为Mixins时,这无疑是一个令人头疼的问题。这里有一些方法可以让你顺利地从JavaScript迁移到TypeScript:target.prototype.someValue=true}}@testAble()classSomeClass{publicsomeValue!:boolean;}constsomeClass=newSomeClass();someClass.someValue//true采用声明合并形式,单独定义接口,使用Decorator扩展属性的类型,放在接口中:interfaceSomeClass{someValue:boolean;}functiontestAble():ClassDecorator{returntarget=>{target.prototype.someValue=true}}@testAble()classSomeClass{}constsomeClass=newSomeClass();someClass.someValue//trueReflectMetadataReflectMetadata是ES7的一个提议,主要用于声明时添加和读取元数据。TypeScript在1.5+版本已经支持它,你只需要:npmireflect-metadata--save。在tsconfig.json中配置emitDecoratorMetadata选项。它有很多使用场景。获取类型信息例如,在vue-property-decorator6.1及以下版本中,PropDecorator可以通过Reflect.getMetadataAPI获取属性类型并传递给Vue。简要代码如下:functionProp():PropertyDecorator{return(target,key:string)=>{consttype=Reflect.getMetadata('design:type',target,key);console.log(`${key}type:${type.name}`);//其他...}}classSomeClass{@Prop()publicAprop!:string;};运行代码可以在控制台看到Aproptype:string。除了获取属性类型外,还可以通过Reflect.getMetadata("design:paramtypes",target,key)和Reflect.getMetadata("design:returntype",target,key)分别获取函数参数类型和返回值类型。自定义metadataKey除了获取类型信息外,常用于自定义metadataKey,并在适当的时候获取其值。示例如下:functionclassDecorator():ClassDecorator{returntarget=>{//在类上定义元数据,键为`classMetaData`,值为`a`Reflect.defineMetadata('classMetaData','a',目标);}}functionmethodDecorator():MethodDecorator{return(target,key,descriptor)=>{//在类中定义原型属性'someMethod'上的元数据,键为`methodMetaData`,值为`b`Reflect.defineMetadata('methodMetaData','b',目标,键);}}@classDecorator()classSomeClass{@methodDecorator()someMethod(){}};Reflect.getMetadata('classMetaData',SomeClass);//'a'Reflect.getMetadata('methodMetaData',newSomeClass(),'someMethod');//'b'examplecontrolInversionandDependencyInjection在Angular2+版本中,InversionofControl和DependencyInjection都是基于这个实现的。现在,让我们实现一个简单的版本:typeConstructor=new(...args:any[])=>T;constInjectable=():ClassDecorator=>target=>{}classOtherService{a=1}@Injectable()classTestService{构造函数(公开阅读只有otherService:OtherService){}testMethod(){console.log(this.otherService.a);}}constFactory=(target:Constructor):T=>{//获取所有注入的服务constproviders=Reflect.getMetadata('design:paramtypes',target);//[OtherService]constargs=providers.map((provider:Constructor)=>newprovider());returnnewtarget(...args);}Factory(TestService).testMethod()//1Controller和Get的实现如果你正在使用TypeScript开发Node应用,相信你对Controller、Get、和POST:@Controller('/test')classSomeClass{@Get('/a')someGetMethod(){return'helloworld';}@Post('/b')somePostMethod(){}};这些Decorators也是基于ReflectMetadata实现的,不同的是这次我们在描述符的值上定义了metadataKey:constMETHOD_METADATA='method';constPATH_METADATA='路径';constController=(path:string):ClassDecorator=>{returntarget=>{Reflect.defineMetadata(PATH_METADATA,path,target);}}constcreateMappingDecorator=(方法:海峡ing)=>(path:string):MethodDecorator=>{return(target,key,descriptor)=>{Reflect.defineMetadata(PATH_METADATA,path,descriptor.value);Reflect.defineMetadata(METHOD_METADATA,method,descriptor.value);}}constGet=createMappingDecorator('GET');constPost=createMappingDecorator('POST');然后,创建一个函数来映射路线:functionmapRoute(instance:Object){constprototype=Object.getPrototypeOf(instance);//过滤掉类的methodNameconstmethodsNames=Object.getOwnPropertyNames(prototype).filter(item=>!isConstructor(item)&&isFunction(prototype[item]));returnmethodsNames.map(methodName=>{constfn=prototype[methodName];//获取定义的元数据constroute=Reflect.getMetadata(PATH_METADATA,fn);constmethod=Reflect.getMetadata(METHOD_METADATA,fn);return{route,方法,fn,方法名}})};您可以获得一些有用的信息:Reflect.getMetadata(PATH_METADATA,某类);//'/test'mapRoute(newSomeClass())/***[{*route:'/a',*method:'GET',*fn:someGetMethod(){...},*methodName:'someGetMethod'*},{*route:'/b',*method:'POST',*fn:somePostMethod(){...},*methodName:'somePostMethod'*}]**/最后,只需将路由相关信息到express或koa。至于为什么要定义在描述符的值上,我们希望mapRoute函数的参数是一个实例,而不是类本身(控制反转)。更巧妙地使用TypeScript(一)深入理解TypeScript