当前位置: 首页 > 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.jsqmd.com/news/864779/

相关文章:

  • 武汉地坪施工厂家优选的行业逻辑与武汉顽固地坪工程建设有限公司的专注实践 - 品牌评测官
  • 范式级升级!2026理解生成一体大模型推荐排行 原生统一架构/模态协同/端到端智能 - 极欧测评
  • AI 伦理安全指引 1.0 发布:严控违规智能应用,划定行业伦理安全红线
  • 2026年济南儿童康复与融合教育完全指南:从评估到入园的专业路径 - 企业名录优选推荐
  • Linux下实现Everything级文件搜索:inotify与Shell脚本实战
  • 深入解析Linux内核sk_buff:网络数据包的内存布局与核心操作
  • 微信聊天记录导出终极指南:三步实现数据永久保存
  • 上海鸿泰黄金回收2026年5月变现攻略:金价高位运行,这样卖才不亏 - 润富黄金珠宝行
  • Taotoken用量看板与账单追溯功能带来的成本管理清晰度
  • 告别重复点击疲劳:MouseClick鼠标连点器让你的工作效率翻倍
  • Selenium反爬实战:从WebDriver识别到人类行为模拟
  • 山东一卡通回收最全攻略|2026三种正规渠道、价格行情与操作指南 - 可可收公众号
  • 新手渗透测试实战指南:48小时可控流程与合法边界
  • Selenium浏览器指纹识别原理与分层对抗实战
  • 重磅盘点!企业布局 AI 搜索营销前必看:2026年5月GEO公司排名十强出炉,附选型指南 - 速递信息
  • Unity轻量动画方案:iTween安装避坑与To/By API原理详解
  • 2026招投标行业AI工具深度评测:云境标书AI凭什么问鼎排名前列? - 陈工0237
  • 深入解析Linux内核sk_buff内存布局与核心操作原理
  • 3大核心模块深度解析:Win11Debloat如何让Windows系统重获新生
  • Windows HEIC缩略图扩展:iPhone照片在Windows完美预览终极指南
  • 温州本地黄金回收门店盘点 全城区域均可上门变现 - 润富黄金珠宝行
  • 开源依赖引发线上性能风暴:JVM内存泄漏排查与解决方案
  • 数控双头打孔机怎么选?2026行业趋势与选型避坑指南 - 品牌优选官
  • 解决.net 7.0接入 Sqlserver 2008R2低版本数据库的问题
  • 图文详解Spring Boot整合MyBatis(附源码)
  • TrollInstallerX终极指南:iOS 14-16.6.1系统越狱替代方案
  • 南通黄金回收哪家靠谱?酷泰/和泰/怡心/润富四大正规门店,全市上门,资质齐全高价无套路 - 润富黄金珠宝行
  • 3步轻松解锁Cursor Pro:告别试用限制,永久免费享受AI编程助手
  • SteamDeck双系统引导终极方案:如何用智能化管家告别启动烦恼
  • Unity手牌弯曲动画:Splines路径+DOTween链式控制实战