当前位置: 首页 > news >正文

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. 组合优于继承:用装饰器组合代替类继承

互动讨论

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

http://www.gsyq.cn/news/1346496.html

相关文章:

  • 武汉地坪施工厂家优选的行业逻辑与武汉顽固地坪工程建设有限公司的专注实践 - 品牌评测官
  • OpenClaw 用户如何通过 CLI 子命令快速写入 Taotoken 配置并开始使用
  • Playwright×CoPilot:用自然语言驱动UI自动化的新范式
  • 如何用Python轻松获取通达信数据:量化投资数据获取的终极指南
  • 银行核心系统接入AI Agent必读:从POC到生产上线的6个生死关卡,第4关92%团队踩坑未察觉
  • 如何快速搭建私人云游戏服务器:Sunshine完整配置指南
  • Moneta Markets亿汇:数字化能力升级的全面观察
  • PUBG罗技鼠标宏压枪脚本:新手也能轻松掌握完美压枪技巧
  • 上海鸿泰黄金回收2026年5月变现攻略:金价高位运行,这样卖才不亏 - 润富黄金珠宝行
  • NVIDIA Profile Inspector:解锁显卡700+隐藏设置的终极优化指南
  • KMS智能激活终极指南:三步永久激活Windows和Office的完整教程
  • 如何在3分钟内为Unity游戏配置实时AI翻译:XUnity.AutoTranslator终极指南
  • 长期使用Taotoken后对账单清晰度与成本预测的体会
  • Supervisely完整指南:5分钟搭建企业级计算机视觉平台
  • 数控双头打孔机怎么选?2026行业趋势与选型避坑指南 - 品牌优选官
  • 解决.net 7.0接入 Sqlserver 2008R2低版本数据库的问题
  • 2026 最新 OpenClaw(小龙虾)部署步骤 小白避坑手册
  • Go 微服务请求链路全景拆解:从 HTTP 到 DB 的一次完整旅行
  • 3步解密QQ聊天记录:全平台数据库密钥提取终极指南
  • SteamDeck双系统引导终极方案:如何用智能化管家告别启动烦恼
  • 3步轻松解锁Cursor Pro:告别试用限制,永久免费享受AI编程助手
  • 2026即墨市本地人必选的瓷砖空鼓专业维修公司TOP5推荐!卫生间空鼓翘边,厨房空鼓翘边,客厅空鼓翘边,全天响应,免费上门,5月专业瓷砖空鼓修复公司持证上岗师傅排名最新深度调研方案) - 一休修缮
  • 南通黄金回收哪家靠谱?酷泰/和泰/怡心/润富四大正规门店,全市上门,资质齐全高价无套路 - 润富黄金珠宝行
  • UE Pak文件解析三步法:魔数校验、索引解析与资源提取
  • 实测4款AI工具,助你通过AI专著写作高效完成20万字专著撰写!
  • 虚幻引擎Pak文件结构解析与Python解包实战
  • 图文详解Spring Boot整合MyBatis(附源码)
  • 2025-2026年跨境电商TRO解冻机构:五家好的产品日常运营防库存积压
  • 监区越界预警技术革命:基于纯视觉无感全域风控体系,重构智慧监所时空管控范式
  • 从频繁Full GC排查到开源工具类性能隐患的实战解析