TypeScript装饰器与元编程实战
TypeScript装饰器与元编程实战作者专注前端开发分享工程化实战经验更新时间2026年5月阅读时长约15分钟前言为什么装饰器是TypeScript的杀手锏如果你使用过Angular或NestJS一定会注意到它们的代码充满了Injectable()、Component()、Controller()这样的魔法标记。这就是**装饰器Decorators**的魔力。装饰器让TypeScript具备了元编程能力——用声明式的方式为类、方法、属性添加额外行为。本文将带你从入门到实战彻底掌握这一强大特性。一、装饰器基础入门1.1 什么是装饰器装饰器是一个函数它能在不修改原始类的前提下给类或类的成员添加额外功能// 简单装饰器functionLogger(target:any,propertyKey:string,descriptor:PropertyDescriptor){constoriginalMethoddescriptor.value;descriptor.valuefunction(...args:any[]){console.log(Calling${propertyKey}with,args);returnoriginalMethod.apply(this,args);};returndescriptor;}classCalculator{Loggeradd(a:number,b:number){returnab;}}constcalcnewCalculator();calc.add(1,2);// 输出: Calling add with [ 1, 2 ]// 返回: 31.2 装饰器的四种类型// 1. 类装饰器functionController(path:string){returnfunction(target:new()any){console.log(Registered controller at /${path});};}// 2. 方法装饰器functionGet(path:string){returnfunction(target:any,propertyKey:string,descriptor:PropertyDescriptor){// 注册路由逻辑};}// 3. 属性装饰器functionAutowired(key:string){returnfunction(target:any,propertyKey:string){// 依赖注入逻辑};}// 4. 参数装饰器functionParam(source:string){returnfunction(target:any,propertyKey:string,index:number){// 参数处理逻辑};}二、类装饰器实战2.1 路由控制器装饰器模拟NestJS的路由装饰器interfaceRouteHandler{path:string;method:string;handler:Function;}functionController(basePath:string){returnfunctionTextendsnew(...args:any[])any(target:T){returnclassextendstarget{privateroutes:RouteHandler[][];constructor(...args:any[]){super(...args);// 注册所有路由this.registerRoutes();}privateregisterRoutes(){// 收集元数据// ...console.log(Controller${target.name}mounted at /${basePath});}};};}functionGet(path:string){returnfunction(target:any,propertyKey:string,descriptor:PropertyDescriptor){constoriginaldescriptor.value;descriptor.valuefunction(...args:any[]){console.log(GET /${path}handled by${propertyKey});returnoriginal.apply(this,args);};returndescriptor;};}// 使用Controller(users)classUserController{Get(list)findAll(){return[{id:1,name:张三}];}Get(:id)findById(id:number){return{id,name:张三};}}2.2 单例模式装饰器functionSingleton(target:new()any){letinstance:anynull;returnnewProxy(target,{construct:function(cls,args){if(!instance){instancenewcls(...args);}returninstance;}});}SingletonclassConfigService{privateconfig{apiUrl:https://api.example.com};get(key:string){return(this.configasany)[key];}}constanewConfigService();constbnewConfigService();console.log(ab);// true三、方法装饰器进阶3.1 缓存装饰器functionCache(ttl:number60000){constcachenewMapstring,{value:any;expires:number}();returnfunction(target:any,propertyKey:string,descriptor:PropertyDescriptor){constoriginaldescriptor.value;descriptor.valuefunction(...args:any[]){constkey${propertyKey}:${JSON.stringify(args)};constcachedcache.get(key);if(cachedcached.expiresDate.now()){console.log(Cache hit for${propertyKey});returncached.value;}constresultoriginal.apply(this,args);// 处理Promiseif(resultinstanceofPromise){returnresult.then((value:any){cache.set(key,{value,expires:Date.now()ttl});returnvalue;});}cache.set(key,{value:result,expires:Date.now()ttl});returnresult;};returndescriptor;};}classUserService{Cache(5000)// 缓存5秒asyncgetUser(id:number){awaitnewPromise(rsetTimeout(r,100));return{id,name:用户id};}}3.2 重试装饰器functionRetry(maxAttempts:number3,delay:number1000){returnfunction(target:any,propertyKey:string,descriptor:PropertyDescriptor){constoriginaldescriptor.value;descriptor.valueasyncfunction(...args:any[]){letlastError:Error;for(letattempt1;attemptmaxAttempts;attempt){try{returnawaitoriginal.apply(this,args);}catch(error){lastErrorerrorasError;console.log(Attempt${attempt}failed, retrying in${delay}ms...);if(attemptmaxAttempts){awaitnewPromise(rsetTimeout(r,delay));}}}throwlastError!;};returndescriptor;};}classApiClient{Retry(3,500)asyncfetch(url:string){if(Math.random()0.7){thrownewError(Network error);}return{data:success};}}四、属性装饰器与依赖注入4.1 简单的依赖注入容器typeConstructorTanynew(...args:any[])T;// 服务容器constContainernewMapConstructor,Constructor();functionInjectable(token?:string){returnfunction(target:Constructor){constttoken||target;Container.set(t,target);console.log(Registered service:${target.name});};}functionInject(token:string){returnfunction(target:any,propertyKey:string){// 替换属性为注入的服务实例Object.defineProperty(target,propertyKey,{get:(){constServiceClassContainer.get(tokenasany);returnnewServiceClass();}});};}// 使用Injectable(UserService)classUserService{getUsers(){return[张三,李四];}}classUserController{Inject(UserService)privateuserService!:UserService;list(){returnthis.userService.getUsers();}}五、装饰器工厂与组合5.1 日志中间件装饰器functionLog(){returnfunction(target:any,propertyKey:string,descriptor:PropertyDescriptor){constisAsyncdescriptor.value.constructor.nameAsyncFunction;if(isAsync){constoriginaldescriptor.value;descriptor.valueasyncfunction(...args:any[]){conststartDate.now();console.log([START]${target.constructor.name}.${propertyKey});try{constresultawaitoriginal.apply(this,args);console.log([END]${propertyKey}took${Date.now()-start}ms);returnresult;}catch(error){console.log([ERROR]${propertyKey}:,error);throwerror;}};}else{constoriginaldescriptor.value;descriptor.valuefunction(...args:any[]){console.log(Calling${propertyKey},args);returnoriginal.apply(this,args);};}returndescriptor;};}// 使用classOrderService{Log()asynccreateOrder(order:any){awaitnewPromise(rsetTimeout(r,100));return{id:1,...order};}Log()calculate(itemCount:number,price:number){returnitemCount*price;}}5.2 权限校验装饰器interfaceRole{name:string;level:number;}constroleLevels:Recordstring,number{guest:0,user:1,admin:2,superadmin:3};functionRequire(levelOrRole:number|string){returnfunction(target:any,propertyKey:string,descriptor:PropertyDescriptor){constrequiredLeveltypeoflevelOrRolenumber?levelOrRole:roleLevels[levelOrRole];constoriginaldescriptor.value;descriptor.valuefunction(...args:any[]){constcurrentUser(globalThisasany).currentUser;// 假设从上下文获取用户级别constuserLevelcurrentUser?.role?.level??0;if(userLevelrequiredLevel){thrownewError(需要权限级别${requiredLevel}当前${userLevel});}returnoriginal.apply(this,args);};returndescriptor;};}// 使用classAdminPanel{Require(2)deleteUser(id:number){console.log(Deleted user${id});}Require(admin)modifySettings(settings:any){console.log(Modified settings);}}六、元数据反射6.1 使用 reflect-metadataimportreflect-metadata;constMETADATA_KEYdesign:paramtypes;functionController(path:string){returnfunction(target:new()any){// 在类上存储路由路径Reflect.defineMetadata(route:path,path,target);// 可以存储更多元数据Reflect.defineMetadata(route:middleware,[],target);returntarget;};}functionGet(path:string){returnfunction(target:any,propertyKey:string){// 存储方法路径Reflect.defineMetadata(route:handler,{path,method:GET},target,propertyKey);// 存储参数类型constparamTypesReflect.getMetadata(METADATA_KEY,target,propertyKey);Reflect.defineMetadata(route:paramTypes,paramTypes,target,propertyKey);};}// 读取元数据functiongetRouteMetadata(target:new()any){return{path:Reflect.getMetadata(route:path,target),handlers:Object.getOwnPropertyNames(target.prototype).filter(kk!constructor).map(k({method:k,config:Reflect.getMetadata(route:handler,target.prototype,k)}))};}七、总结与实践建议装饰器类型用景经典案例类装饰器依赖注入、单例、AOPNestJS、Angular方法装饰器缓存、重试、事务数据访问层参数装饰器参数校验、转换API 入参处理属性装饰器依赖注入、自动装配IoC 容器最佳实践不要过度装饰每个装饰器都有开销保持合理的抽象层级类型安全充分利用TypeScript的类型系统调试友好装饰器会增加调用栈添加有意义的日志组合优于继承用装饰器组合代替类继承互动讨论你在项目中使用装饰器了吗遇到过什么问题