TypeScript 装饰器与元编程:从类装饰到参数装饰

背景与问题界定 TypeScript 装饰器经历了从实验性特性到 ECMAScript 标准的演进,目前 5.x 版本同时支持传统的实验性装饰器和 ESMirror 规范的新式装饰器。然而,许多团队对装饰器的认知仍然停留在"给类加个标记"的层面,没有充分发挥其在 AOP 侧切面编程中的能力。实际工程中,基础设施代码与业务逻辑的横切关注点混杂——每个 Controller 方法开头重复的权限检查、参数校验、日志记录形成了大量模板代码。Decorator 正好是解决这类横切问题的天然工具。但同时也面临着类型安全、装饰器执行顺序、以及新旧装饰器语法兼容性等工程挑战。 目标拆解与工程约束 装饰器类型安全:自定义装饰器应使用泛型保留被装饰目标的原始类型签名,不破坏 TypeScript 的静态类型推导。 执行顺序可预测:多个装饰器叠加时,执行顺序遵循"自下而上应用、自外向内执行"的契约,团队应有统一规范记录装饰器组合的语义。 新旧装饰器兼容:项目在迁移到 Stage 3 装饰器过程中,提供过渡工具确保两种装饰器语法能在不同模块中共存。 运行时开销可控:装饰器的元数据解析和代理创建不应显著影响热路径性能,需要提供 Benchmark 基准。 方案设计 我们将装饰器的应用场景分为四类,分别对应四种装饰器类型:类装饰器用于依赖注入容器注册和单例管理;方法装饰器用于日志切片、权限校验和重试控制;访问器装饰器用于计算属性的缓存和延迟加载;参数装饰器用于参数验证和依赖注入的参数标记。 以方法装饰器为例,一个通用的日志装饰器实现可以在不侵入业务逻辑的前提下,自动采集方法调用的入参、返回值和耗时: function Log(level: 'info' | 'warn' | 'error' = 'info') { return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) { const original = descriptor.value descriptor.value = function (...args: any[]) { const start = performance.now() try { const result = original.apply(this, args) if (result instanceof Promise) { return result.then(r => { logger.log(level, `${propertyKey} succeeded`, { args, duration: performance.now() - start }) return r }).catch(err => { logger.error(`${propertyKey} failed`, { args, error: err }) throw err }) } logger.log(level, `${propertyKey} succeeded`, { args, duration: performance.now() - start }) return result } catch (err) { logger.error(`${propertyKey} failed`, { args, error: err }) throw err } } return descriptor } } 对于依赖注入场景,我们使用类装饰器 + reflect-metadata 实现一个轻量级的 DI 容器。通过在构造函数的参数上使用 @Inject 参数装饰器标记依赖,容器在实例化时自动解析依赖树并注入。这种方法在 NestJS 中已被大规模验证,但在纯前端项目中同样适用——只需要一个简洁的容器实现和几个装饰器辅助函数。 ...

2026年8月6日 · 1 分钟 · BvBeJ