在 TypeScript 5.0 中,TS 正式支持了 TC39 Stage 3 标准装饰器(Standard Decorators) 。这是 ECMAScript 规范原生的装饰器方案,代表了 JS/TS 装饰器的未来方向。
与旧版(Legacy/Stage 1)相比,标准装饰器不再依赖 experimentalDecorators 配置 experimentalDecorators,彻底解决了与 JS 原生私有字段(#field)不兼容的问题,并且引入了类型安全且不依赖额外包的元数据机制。
一、核心机制:统一签名与 Context 对象
TS 5.0+ 标准装饰器的基础形式非常简单,所有装饰器函数都接收两个参数:
arduino
function myDecorator(value: any, context: DecoratorContext) {
// value: 被装饰的目标(类、方法、访问器等)
// context: 上下文对象,包含目标的元数据与控制函数
}
context 上下文对象包含的字段
| 属性名 | 类型 | 说明 |
|---|---|---|
| kind | string |
目标类型:'class' /'method'/'getter' / 'setter' / 'field' / 'accessor' |
| name | string/symbol |
被装饰的成员名称或类名 |
| static | boolean |
是否为静态成员(类装饰器时为 undefined) |
| private | boolean |
是否为原生私有成员(如 #name) |
| access | object |
包含 get / set 方法,用于在运行时动态读取或修改成员 |
| addInitializer | function |
注册一个初始化回调函数(在实例创建或类声明时触发) |
| metadata | object |
挂载在类上的元数据字典(基于 Symbol.metadata) |
二、5 大类标准装饰器详解与类型定义
TS 为每一种装饰器都内置了严格的泛型上下文类型(如 ClassMethodDecoratorContext)。
1. 方法装饰器 (Method Decorators)
- 签名 :
(value: TargetFunction, context: ClassMethodDecoratorContext) => ReplacementFunction | void - 作用:拦截方法调用、记录日志、防抖/节流、异常捕捉。
typescript
function LogExecution<This, Args extends any[], Return>(
target: (this: This, ...args: Args) => Return,
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
) {
const methodName = String(context.name);
// 返回一个新的函数来替换原方法
return function (this: This, ...args: Args): Return {
console.log(`[LOG] Calling ${methodName} with args:`, args);
const result = target.call(this, ...args);
console.log(`[LOG] ${methodName} returned:`, result);
return result;
};
}
class Calculator {
@LogExecution
add(a: number, b: number) {
return a + b;
}
}
const calc = new Calculator();
calc.add(2, 3); // 自动打印调用日志和返回值
2. 类装饰器 (Class Decorators)
- 签名 :
(value: Constructor, context: ClassDecoratorContext) => ReplacementConstructor | void - 作用:扩展类、混入(Mixin)新功能、注册全局类服务。
typescript
function Timestamped<T extends abstract new (...args: any[]) => any>(
target: T,
context: ClassDecoratorContext<T>
) {
// 返回继承自原类的子类
return class extends target {
createdAt = new Date();
};
}
@Timestamped
class User {
constructor(public name: string) {}
}
const user = new User("Alice");
console.log((user as any).createdAt); // 访问装饰器注入的属性
3. 属性/字段装饰器 (Field Decorators)
- 关键区别 :字段在类定义时尚未赋值 ,因此第一个参数
value恒为undefined。 - 签名 :
(value: undefined, context: ClassFieldDecoratorContext) => InitializerFunction | void - 返回值 :返回一个接收初始值并返回新值的函数
(initialValue: T) => T。
javascript
function DefaultValue<T>(defaultValue: T) {
return function (value: undefined, context: ClassFieldDecoratorContext) {
// 返回初始化回调函数
return function (initialValue: T) {
return initialValue ?? defaultValue;
};
};
}
class Profile {
@DefaultValue("Guest")
username?: string;
}
const p1 = new Profile();
console.log(p1.username); // "Guest"
4. 自动访问器装饰器 (Auto-Accessor Decorators)
TS 5.0 配合 ES 标准引入了全新的 accessoraccessor 语法。accessor 会自动生成私有存储字段以及对应的 getter 和 setter。
- 签名 :
(value: { get: () => T, set: (v: T) => void }, context: ClassAccessorDecoratorContext) => { get?, set?, init? } | void - 作用:同时拦截读取和写入,是实现响应式系统(如 Vue Reactivity / Signals)的利器。
typescript
function MinMax(min: number, max: number) {
return function <T, V extends number>(
target: ClassAccessorDecoratorTarget<T, V>,
context: ClassAccessorDecoratorContext<T, V>
): ClassAccessorDecoratorResult<T, V> {
return {
set(value: V) {
if (value < min || value > max) {
throw new Error(`属性 ${String(context.name)} 必须在 ${min} 和 ${max} 之间!`);
}
target.set.call(this, value);
}
};
};
}
class Player {
// 使用 accessor 关键字声明自动访问器
@MinMax(0, 100)
accessor health = 100;
}
const p = new Player();
p.health = 50; // 正常
// p.health = 150; // 报错: 属性 health 必须在 0 和 100 之间!
5. Accessor (Getter / Setter) 装饰器
如果你显式编写了 get 或 set 方法,可以分别进行拦截。
typescript
function Precise(decimalPlaces: number) {
return function <This, Return extends number>(
target: (this: This) => Return,
context: ClassGetterDecoratorContext<This, Return>
) {
return function (this: This): Return {
const result = target.call(this);
return Number(result.toFixed(decimalPlaces)) as Return;
};
};
}
class Circle {
constructor(public radius: number) {}
@Precise(2)
get area() {
return Math.PI * this.radius * this.radius;
}
}
const c = new Circle(5);
console.log(c.area); // 78.54
三、高级核心特性
1. 使用 addInitializer 实现自动绑定 (@bound)
在过去的 TS 中实现 this 绑定通常要修改描述符,而在标准装饰器中,官方提供了 context.addInitializer 方法。它会在**实例构造完成时(构造函数末尾)**自动调用:
typescript
function bound<This, Args extends any[], Return>(
target: (this: This, ...args: Args) => Return,
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
) {
const methodName = context.name;
// 注册初始化函数
context.addInitializer(function (this: any) {
// this 指向正在创建的实例
this[methodName] = this[methodName].bind(this);
});
}
class Button {
constructor(private label: string) {}
@bound
onClick() {
console.log(`Clicked: ${this.label}`);
}
}
const btn = new Button("Submit");
const clickHandler = btn.onClick;
clickHandler(); // 输出 "Clicked: Submit",this 不会丢失!
2. 支持原生私有字段 (#field)
旧版装饰器由于使用字符串 key 操作 prototype,无法作用于 JS 原生私有成员。TS 5.0+ 标准装饰器完美支持 # 私有成员:
typescript
function WatchPrivate(target: any, context: ClassMethodDecoratorContext) {
console.log(`私有方法 ${String(context.name)} 已成功装饰,private=${context.private}`); // private = true
}
class SecretService {
#internalKey = "123456";
@WatchPrivate
#getSecret() {
return this.#internalKey;
}
}
3. 标准元数据支持 (context.metadata)
标准装饰器废弃了 reflect-metadata 第三方库,引入了标准的 context.metadata 对象。该对象会在同类所有装饰器共享 ,最终挂载到 Symbol.metadata 上。
less
function Tag(tagName: string) {
return function (target: any, context: ClassMethodDecoratorContext) {
// 给 context.metadata 写入元数据
context.metadata[context.name] = tagName;
};
}
class ApiController {
@Tag("GET_USERS")
getUsers() {}
@Tag("POST_USER")
createUser() {}
}
// 读取类上的原生元数据
const metadata = ApiController[Symbol.metadata];
console.log(metadata);
// 输出: { getUsers: 'GET_USERS', createUser: 'POST_USER' }
四、执行顺序全解
当在一个类中混合使用多种装饰器时,其执行生命周期分为两个阶段:
-
求值与注入阶段 (Evaluation & Decorator Application) :
-
在类定义时按照以下顺序评估并施加装饰器:
- 实例成员(方法、访问器、字段)
- 静态成员(方法、访问器、字段)
- 类装饰器
-
对同一个目标 挂载多个装饰器时,按洋葱模型(自下而上 / 由内而外)执行。
-
-
初始化回调阶段 (Initializers Execution) :
- 当
new Class()被调用时,按照字段定义顺序依次触发由addInitializer注册的回调。
- 当
五、标准 vs 旧版 关键差异总结表
| 对比维度 | Standard 标准装饰器 (TS 5.0+) | Legacy 实验性装饰器 (experimentalDecorators) |
|---|---|---|
| 标准状态 | TC39 Stage 3 (官方标准) | TC39 Stage 1 (已废弃/过时) |
| 参数装饰器 | ❌ 暂不支持 (Stage 3 暂未包含) | ✅ 支持 (如 NestJS @Param()) |
| 原生理私有成员 | ✅ 完美支持 (#private) |
❌ 无法支持 |
| 类型安全 | 高(强制使用泛型约束 This 类型) |
一般 |
thisthis 绑定 |
依赖 context.addInitializer |
修改 PropertyDescriptor.get |
| 元数据存储 | 借助内置 context.metadata |
依赖 reflect-metadata 库 |