TypeScript 装饰器详解
装饰器是实验性功能,用于声明式地添加横切逻辑,下面梳理各类装饰器用法。
注意:需在
tsconfig.json中开启"experimentalDecorators": true。
类装饰器
类装饰器接收构造函数作为参数,可修改或替换它:
TypeScript
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
@sealed
class Greeter {
greet() { console.log("Hello"); }
}
装饰器工厂(返回装饰器函数)
TypeScript
function logClass(prefix: string) {
return function (constructor: Function) {
console.log(`${prefix} ${constructor.name} created`);
};
}
@logClass("Creating:")
class UserService {}
// 输出:Creating: UserService created
方法装饰器
方法装饰器接收三个参数:原型对象、方法名、属性描述符:
TypeScript
function log(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${propertyKey} with`, args);
return original.apply(this, args);
};
}
class Calculator {
@log
add(a: number, b: number): number {
return a + b;
}
}
const calc = new Calculator();
calc.add(1, 2); // 输出:Calling add with [1, 2]
属性装饰器
属性装饰器接收两个参数:原型对象、属性名:
TypeScript
function Min(min: number) {
return function (target: any, propertyKey: string) {
let value: number;
Object.defineProperty(target, propertyKey, {
get() { return value; },
set(newVal: number) {
if (newVal < min) {
throw new Error(`${propertyKey} must be >= ${min}`);
}
value = newVal;
}
});
};
}
class Product {
@Min(0)
price!: number;
}
const p = new Product();
// p.price = -5; // ❌ Error
p.price = 10; // ✅
访问器装饰器
与方法装饰器用法相同,应用于 getter/setter:
TypeScript
function enumerable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.enumerable = value;
};
}
class Person {
constructor(private _name: string) {}
@enumerable(false)
get name() { return this._name; }
}
const p = new Person("Alice");
console.log(Object.keys(p)); // [](name 不可枚举)
参数装饰器
参数装饰器接收三个参数:原型对象、方法名、参数索引:
TypeScript
function Required(target: any, propertyKey: string, parameterIndex: number) {
console.log(`${propertyKey} param ${parameterIndex} is required`);
}
class AuthService {
login(@Required username: string, @Required password: string) {
console.log(`Login: ${username}`);
}
}
// 输出:login param 0 is required
// login param 1 is required
参数装饰器与元数据结合
TypeScript
const REQUIRED_METADATA_KEY = "required_params";
function Required(target: any, propertyKey: string, parameterIndex: number) {
const existing = Reflect.getMetadata(REQUIRED_METADATA_KEY, target, propertyKey) || [];
existing.push(parameterIndex);
Reflect.defineMetadata(REQUIRED_METADATA_KEY, existing, target, propertyKey);
}
装饰器执行顺序
TypeScript
function classDec(constructor: Function) { console.log("Class"); }
function methodDec(target: any, key: string, desc: PropertyDescriptor) { console.log("Method"); }
function propDec(target: any, key: string) { console.log("Property"); }
function paramDec(target: any, key: string, idx: number) { console.log("Param"); }
@classDec
class Foo {
@propDec bar: string;
@methodDec
test(@paramDec x: number) {}
}
// 执行顺序:Property → Param → Method → Class
规则:从上到下,从外到内。类装饰器最后执行,参数装饰器最先。
要点总结
- 类装饰器接收构造函数,可修改/替换/扩展它
- 方法装饰器通过
descriptor.value包装原方法实现横切逻辑 - 属性装饰器通过
Object.defineProperty重写 getter/setter - 访问器装饰器与方法装饰器相同,应用于 getter/setter
- 参数装饰器记录参数索引,常与元数据反射结合使用
- 装饰器执行顺序:属性 → 参数 → 方法 → 类(从外到内)
📝 发现内容有误?点击此处直接编辑