news 2026/5/22 13:54:59

TypeScript装饰器与元编程实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
TypeScript装饰器与元编程实战

TypeScript装饰器与元编程实战

作者:专注前端开发,分享工程化实战经验
更新时间:2026年5月
阅读时长:约15分钟


前言:为什么装饰器是TypeScript的杀手锏?

如果你使用过Angular或NestJS一定会注意到:它们的代码充满了@Injectable()@Component()@Controller()这样的"魔法标记"。这就是**装饰器(Decorators)**的魔力。

装饰器让TypeScript具备了元编程能力——用声明式的方式为类、方法、属性添加额外行为。本文将带你从入门到实战,彻底掌握这一强大特性。


一、装饰器基础入门

1.1 什么是装饰器?

装饰器是一个函数,它能在不修改原始类的前提下,给类或类的成员添加额外功能:

// 简单装饰器functionLogger(target:any,propertyKey:string,descriptor:PropertyDescriptor){constoriginalMethod=descriptor.value;descriptor.value=function(...args:any[]){console.log(`Calling${propertyKey}with`,args);returnoriginalMethod.apply(this,args);};returndescriptor;}classCalculator{@Loggeradd(a:number,b:number){returna+b;}}constcalc=newCalculator();calc.add(1,2);// 输出: Calling add with [ 1, 2 ]// 返回: 3

1.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){returnfunction<Textendsnew(...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){constoriginal=descriptor.value;descriptor.value=function(...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:any=null;returnnewProxy(target,{construct:function(cls,args){if(!instance){instance=newcls(...args);}returninstance;}});}@SingletonclassConfigService{privateconfig={apiUrl:"https://api.example.com"};get(key:string){return(this.configasany)[key];}}consta=newConfigService();constb=newConfigService();console.log(a===b);// true

三、方法装饰器进阶

3.1 缓存装饰器

functionCache(ttl:number=60000){constcache=newMap<string,{value:any;expires:number}>();returnfunction(target:any,propertyKey:string,descriptor:PropertyDescriptor){constoriginal=descriptor.value;descriptor.value=function(...args:any[]){constkey=`${propertyKey}:${JSON.stringify(args)}`;constcached=cache.get(key);if(cached&&cached.expires>Date.now()){console.log(`Cache hit for${propertyKey}`);returncached.value;}constresult=original.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(r=>setTimeout(r,100));return{id,name:"用户"+id};}}

3.2 重试装饰器

functionRetry(maxAttempts:number=3,delay:number=1000){returnfunction(target:any,propertyKey:string,descriptor:PropertyDescriptor){constoriginal=descriptor.value;descriptor.value=asyncfunction(...args:any[]){letlastError:Error;for(letattempt=1;attempt<=maxAttempts;attempt++){try{returnawaitoriginal.apply(this,args);}catch(error){lastError=errorasError;console.log(`Attempt${attempt}failed, retrying in${delay}ms...`);if(attempt<maxAttempts){awaitnewPromise(r=>setTimeout(r,delay));}}}throwlastError!;};returndescriptor;};}classApiClient{@Retry(3,500)asyncfetch(url:string){if(Math.random()>0.7){thrownewError("Network error");}return{data:"success"};}}

四、属性装饰器与依赖注入

4.1 简单的依赖注入容器

typeConstructor<T=any>=new(...args:any[])=>T;// 服务容器constContainer=newMap<Constructor,Constructor>();functionInjectable(token?:string){returnfunction(target:Constructor){constt=token||target;Container.set(t,target);console.log(`Registered service:${target.name}`);};}functionInject(token:string){returnfunction(target:any,propertyKey:string){// 替换属性为注入的服务实例Object.defineProperty(target,propertyKey,{get:()=>{constServiceClass=Container.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){constisAsync=descriptor.value.constructor.name==="AsyncFunction";if(isAsync){constoriginal=descriptor.value;descriptor.value=asyncfunction(...args:any[]){conststart=Date.now();console.log(`[START]${target.constructor.name}.${propertyKey}`);try{constresult=awaitoriginal.apply(this,args);console.log(`[END]${propertyKey}took${Date.now()-start}ms`);returnresult;}catch(error){console.log(`[ERROR]${propertyKey}:`,error);throwerror;}};}else{constoriginal=descriptor.value;descriptor.value=function(...args:any[]){console.log(`Calling${propertyKey}`,args);returnoriginal.apply(this,args);};}returndescriptor;};}// 使用classOrderService{@Log()asynccreateOrder(order:any){awaitnewPromise(r=>setTimeout(r,100));return{id:1,...order};}@Log()calculate(itemCount:number,price:number){returnitemCount*price;}}

5.2 权限校验装饰器

interfaceRole{name:string;level:number;}constroleLevels:Record<string,number>={guest:0,user:1,admin:2,superadmin:3};functionRequire(levelOrRole:number|string){returnfunction(target:any,propertyKey:string,descriptor:PropertyDescriptor){constrequiredLevel=typeoflevelOrRole==="number"?levelOrRole:roleLevels[levelOrRole];constoriginal=descriptor.value;descriptor.value=function(...args:any[]){constcurrentUser=(globalThisasany).currentUser;// 假设从上下文获取用户级别constuserLevel=currentUser?.role?.level??0;if(userLevel<requiredLevel){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-metadata

import"reflect-metadata";constMETADATA_KEY="design: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);// 存储参数类型constparamTypes=Reflect.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(k=>k!=="constructor").map(k=>({method:k,config:Reflect.getMetadata("route:handler",target.prototype,k)}))};}

七、总结与实践建议

装饰器类型���用���景经典案例
类装饰器依赖注入、单例、AOPNestJS、Angular
方法装饰器缓存、重试、事务数据访问层
参数装饰器参数校验、转换API 入参处理
属性装饰器依赖注入、自动装配IoC 容器

最佳实践

  1. 不要过度装饰:每个装饰器都有开销,保持合理的抽象层级
  2. 类型安全:充分利用TypeScript的类型系统
  3. 调试友好:装饰器会增加调用栈,添加有意义的日志
  4. 组合优于继承:用装饰器组合代替类继承

互动讨论

你在项目中使用装饰器了吗?遇到过什么问题?

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/22 13:54:56

RTL设计规范全解析:从代码风格到AXI4-Lite实战

1. 项目概述&#xff1a;从“能跑”到“跑得好”的RTL设计之路刚入行做数字芯片前端设计那会儿&#xff0c;总觉得写RTL&#xff08;寄存器传输级&#xff09;代码就像写软件&#xff0c;逻辑功能实现了&#xff0c;仿真波形对了&#xff0c;就算大功告成。直到后来在流片后调试…

作者头像 李华
网站建设 2026/5/22 13:54:46

Flutter图片加载全解析:从Widget到GPU渲染的性能优化实践

1. 项目概述&#xff1a;从“显示一张图”到理解整个渲染管线在Flutter项目里&#xff0c;加一张图片大概是新手最先学会的几个操作之一&#xff0c;Image.asset(assets/logo.png)或者Image.network(https://...)一行代码&#xff0c;图片就出来了。看起来简单得不能再简单&…

作者头像 李华
网站建设 2026/5/22 13:53:23

量子声子激光器:双离子系统实现量子区域相干声场

1. 从光到声&#xff1a;声子激光器的概念与挑战在量子物理和精密测量的世界里&#xff0c;激光器早已不是什么新鲜事物。从超市的扫码器到实验室里的光镊&#xff0c;从光纤通信到引力波探测&#xff0c;相干性极高的激光光场已经成为我们探索和改造世界不可或缺的工具。但你是…

作者头像 李华
网站建设 2026/5/22 13:51:08

2026年主流AI论文写作软件全攻略(含保姆级操作教程)

以下是当前学术圈口碑TOP的6款AI写论文工具&#xff0c;覆盖从选题、开题到降重、答辩的论文全流程&#xff0c;剔除冗余工具&#xff0c;每款均附分步骤实操指南场景适配技巧&#xff0c;重点突出中文论文适配性&#xff0c;新手也能快速上手&#xff0c;效率翻倍。一、全流程…

作者头像 李华