在 TypeScript 中使用装饰器之前,需要确保在 tsconfig.json 中开启了 experimentalDecorators:
json
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true // 如果需要反射元数据
}
}
一、简介 (Introduction)
什么是装饰器?
装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、访问器(get/set)、属性或参数上。本质上,装饰器就是一个在运行时被调用的函数。
核心特点:
- 使用
@expression语法。 - 用于修改类或其成员的行为,不改变原有代码结构。
- 目前 TypeScript 装饰器主要遵循早期的 Stage 2 提案(在 Angular、NestJS 等框架中被广泛使用)。
二、类装饰器 (Class Decorators)
定义: 声明在类声明之前,应用于类构造函数。
签名: (constructor: Function) => void | Function
执行时机: 类定义时(而不是实例化时)执行。
typescript
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
console.log("类装饰器执行了");
}
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
}
// 输出: 类装饰器执行了
如果类装饰器返回一个新的构造函数,它会替换原来的类定义。
三、装饰器工厂 (Decorator Factories)
定义: 如果想给装饰器传递参数,就必须使用装饰器工厂。它本质上是一个返回装饰器函数的函数。
签名: (...args: any[]) => (target: any, ...) => void
typescript
function color(value: string) { // 这就是装饰器工厂
return function (constructor: Function) { // 这是真正的装饰器
console.log(`给类 ${constructor.name} 应用了颜色: ${value}`);
};
}
@color("红色")
class Car {}
// 输出: 给类 Car 应用了颜色: 红色
四、装饰器组合 (Decorator Composition)
定义: 在同一个目标上可以应用多个装饰器。
执行顺序(非常重要):
- 表达式从上往下求值 (即先执行
@f(),再执行@g())。 - 结果从下往上调用 (即先执行
@g返回的函数,再执行@f返回的函数)。
typescript
function f() {
console.log("f(): evaluated");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("f(): called");
};
}
function g() {
console.log("g(): evaluated");
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("g(): called");
};
}
class C {
@f()
@g()
method() {}
}
// 输出顺序:
// f(): evaluated
// g(): evaluated
// g(): called
// f(): called
五、属性装饰器 (Property Decorators)
定义: 声明在属性声明之前。
签名: (target: Object, propertyKey: string | symbol) => void
注意: 属性装饰器没有 descriptor(属性描述符),因为属性通常在构造函数中初始化,而不是在原型上。
typescript
function format(target: any, propertyKey: string) {
console.log(`属性装饰器作用于: ${propertyKey}`);
}
class User {
@format
name: string = "张三";
}
应用场景:配合 reflect-metadata 做数据校验、序列化等。
六、方法装饰器 (Method Decorators)
定义: 声明在方法声明之前。
签名: (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => PropertyDescriptor | void
用途: 修改、替换或包装方法。这是最常用的装饰器之一(如日志记录、性能监控、修改返回值)。
typescript
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`调用方法: ${propertyKey},参数: ${JSON.stringify(args)}`);
return originalMethod.apply(this, args);
};
}
class Calculator {
@log
add(a: number, b: number) {
return a + b;
}
}
new Calculator().add(1, 2);
// 输出: 调用方法: add,参数: [1,2]
七、访问器装饰器 (Accessor Decorators)
定义: 声明在 getter 或 setter 之前。
签名: 与方法装饰器相同 (target, propertyKey, descriptor)。
注意: TypeScript 不允许同时装饰同一个成员的 get 和 set 访问器,只能选择其中一个装饰,因为两者的属性描述符是合并在一起的。
typescript
function configurable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value;
};
}
class Point {
private _x: number = 0;
@configurable(false)
get x() { return this._x; }
}
八、参数装饰器 (Parameter Decorators)
定义: 声明在方法参数或构造函数参数之前。
签名: (target: Object, propertyKey: string | symbol, parameterIndex: number) => void
执行时机: 参数装饰器会在类装饰器、方法装饰器之前 被调用。
用途: 提取参数元数据,常用于依赖注入(DI)框架(如 Angular、NestJS)。
typescript
function inject(target: any, propertyKey: string, parameterIndex: number) {
console.log(`参数装饰器: 方法 ${propertyKey} 的第 ${parameterIndex} 个参数需要注入`);
}
class Service {
constructor(@inject private db: any) {}
query(@inject data: string) {
// ...
}
}
// 输出:
// 参数装饰器: 方法 query 的第 0 个参数需要注入
// 参数装饰器: 方法 undefined 的第 0 个参数需要注入 (构造函数中 propertyKey 为 undefined)
💡 总结执行顺序
对于同一个类,不同类型的装饰器执行顺序如下:
- 参数装饰器 (Parameter)
- 方法/访问器/属性装饰器 (Method/Accessor/Property)
- 类装饰器 (Class)
而同类装饰器组合时,遵循从上到下求值,从下到上执行的原则。