TypeScript 装饰器怎样使用?标准装饰器和 experimentalDecorators 有什么区别?

先理解原理和示例,再练习完整口述与追问。

装饰器是在类及其成员定义时运行的函数,用于附加元数据或包装行为,常见于 NestJS、TypeORM、Angular。TypeScript 5.0 起支持 TC39 标准装饰器,签名是 (value, context);旧版 experimentalDecorators 签名是 (target, key, descriptor),支持参数装饰器和 emitDecoratorMetadata,许多框架仍依赖旧版。

标准装饰器的 context 包含 kind(class、method、getter、setter、field、accessor)、name、static、private 和 addInitializer。方法装饰器返回一个新函数来替换原方法;字段装饰器返回初始化函数,用于转换初始值。多个装饰器写在同一目标上时,表达式从上到下求值,应用顺序从下到上(离目标最近的先应用)。

旧版装饰器通过修改属性描述符生效,配合 reflect-metadata 和 emitDecoratorMetadata 可在运行时读取参数类型,NestJS 的依赖注入依赖此能力;标准装饰器目前没有参数装饰器,也不输出类型元数据。两种实现不能混用,tsconfig 开启 experimentalDecorators 就使用旧语义。

ts
function once<This, Args extends unknown[], Return>(
  method: (this: This, ...args: Args) => Return,
  context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
) {
  const results = new WeakMap<object, Return>()
  return function (this: This, ...args: Args): Return {
    const key = this as object
    if (!results.has(key)) results.set(key, method.apply(this, args))
    return results.get(key) as Return
  }
}

class Config {
  @once
  load() { console.log('只读取一次'); return { theme: 'dark' } }
}

WeakMap 以实例为键缓存结果,每个实例各自只执行一次,实例被回收后缓存也随之释放。

装饰器在类定义时执行,而不是每次实例化时;把业务逻辑大量藏在装饰器中会降低可读性。esbuild、Babel 等工具对装饰器版本的支持需要单独配置。

装饰器是在类定义时运行、用来包装类或成员的函数,常用于日志、缓存、依赖注入和 ORM 映射。TS 5.0 起支持标准装饰器,签名是 value 加 context,可以替换方法或用 addInitializer 注册初始化逻辑;旧的 experimentalDecorators 签名是 target、key、descriptor,支持参数装饰器和类型元数据,NestJS 等框架仍在用。多个装饰器从下往上应用,两种语义不能混用。