TypeScript 装饰器详解
装饰器(Decorator)是 TypeScript 中的一个重要特性,它提供了一种声明式的方式来修改类、方法、属性或参数的行为。装饰器使用 @expression 语法,其中 expression 是一个函数,它会在运行时被调用,并接收被装饰目标的元数据。
1. 装饰器基础
1.1. 启用装饰器
在 tsconfig.json 中启用实验性装饰器支持:
{"compilerOptions": {"experimentalDecorators": true,"emitDecoratorMetadata": true}
}
2. 装饰器类型
2.1. 类装饰器
类装饰器应用于类构造函数,可以用来观察、修改或替换类定义。
function sealed(constructor: Function) {Object.seal(constructor);Object.seal(constructor.prototype);
}@sealed
class Greeter {greeting: string;constructor(message: string) {this.greeting = message;}greet() {return "Hello, " + this.greeting;}
}
通过定义类装饰器工厂还可以传递参数给装饰器:
function color(value: string) {return function (constructor: Function) {constructor.prototype.color = value;};
}@color("red")
class Car {// ...
}const myCar = new Car();
console.log((myCar as any).color); // 输出: red
2.2. 方法装饰器
方法装饰器应用于方法的属性描述符,可以用来观察、修改或替换方法定义。
function enumerable(value: boolean) {return function (target: any,propertyKey: string,descriptor: PropertyDescriptor) {descriptor.enumerable = value;};
}class Greeter {greeting: string;constructor(message: string) {this.greeting = message;}@enumerable(false)greet() {return "Hello, " + this.greeting;}
}
2.3. 访问器装饰器
访问器装饰器应用于访问器的属性描述符,可以用来观察、修改或替换访问器的定义。
function configurable(value: boolean) {return function (target: any,propertyKey: string,descriptor: PropertyDescriptor) {descriptor.configurable = value;};
}class Point {private _x: number;private _y: number;constructor(x: number, y: number) {this._x = x;this._y = y;}@configurable(false)get x() {return this._x;}@configurable(false)get y() {return this._y;}
}
2.4. 属性装饰器
属性装饰器应用于类的属性。
function format(formatString: string) {return function (target: any, propertyKey: string) {let value = target[propertyKey];const getter = function () {return `${formatString} ${value}`;};const setter = function (newVal: string) {value = newVal;};Object.defineProperty(target, propertyKey, {get: getter,set: setter,enumerable: true,configurable: true,});};
}class Greeter {@format("Hello")greeting: string;constructor(message: string) {this.greeting = message;}
}const greeter = new Greeter("World");
console.log(greeter.greeting); // 输出: Hello World
2.5. 参数装饰器
参数装饰器应用于构造函数或方法的一个参数。
function validate(target: any,propertyKey: string,parameterIndex: number
) {const validParams: number[] = Reflect.getOwnMetadata("validParams", target, propertyKey) || [];validParams.push(parameterIndex);Reflect.defineMetadata("validParams", validParams, target, propertyKey);
}class Greeter {greet(@validate name: string) {return "Hello " + name;}
}
3. 装饰器执行顺序
装饰器的应用顺序如下:
1. 参数装饰器,然后依次是方法装饰器、访问器装饰器或属性装饰器应用到每个实例成员;
2. 参数装饰器,然后依次是方法装饰器、访问器装饰器或属性装饰器应用到每个静态成员;
3. 参数装饰器应用到构造函数;
4. 类装饰器应用到类;
4. 实际应用示例
4.1. 日志装饰器
function log(target: any,propertyKey: string,descriptor: PropertyDescriptor
) {const originalMethod = descriptor.value;descriptor.value = function (...args: any[]) {console.log(`Calling ${propertyKey} with args: ${JSON.stringify(args)}`);const result = originalMethod.apply(this, args);console.log(`Called ${propertyKey}, returned: ${JSON.stringify(result)}`);return result;};return descriptor;
}class Calculator {@logadd(a: number, b: number) {return a + b;}
}const calculator = new Calculator();
calculator.add(2, 3);
// 输出:
// Calling add with args: [2,3]
// Called add, returned: 5
4.2. 验证装饰器
function validateEmail(target: any,propertyKey: string,parameterIndex: number
) {const validateParams: number[] = Reflect.getOwnMetadata("validateParams", target, propertyKey) || [];validateParams.push(parameterIndex);Reflect.defineMetadata("validateParams", validateParams, target, propertyKey);
}function validate(target: any,propertyKey: string,descriptor: PropertyDescriptor
) {const originalMethod = descriptor.value;const validateParams: number[] = Reflect.getOwnMetadata("validateParams", target, propertyKey) || [];descriptor.value = function (...args: any[]) {for (const index of validateParams) {if (!/^\S+@\S+\.\S+$/.test(args[index])) {throw new Error(`Invalid email: ${args[index]}`);}}return originalMethod.apply(this, args);};return descriptor;
}class UserService {@validatecreateUser(name: string,@validateEmail email: string,age: number) {return { name, email, age };}
}const userService = new UserService();
userService.createUser("Alice", "alice@example.com", 25); // 正常
userService.createUser("Bob", "invalid-email", 30); // 抛出错误
4.3. 依赖注入装饰器
const serviceRegistry = new Map<string, any>();function Injectable(constructor: Function) {serviceRegistry.set(constructor.name, new constructor());
}function Inject(serviceName: string) {return function (target: any, propertyKey: string) {Object.defineProperty(target, propertyKey, {get: () => serviceRegistry.get(serviceName),enumerable: true,configurable: true,});};
}@Injectable
class LoggerService {log(message: string) {console.log(`[LOG]: ${message}`);}
}class AppComponent {@Inject("LoggerService")logger!: LoggerService;run() {this.logger.log("Application started");}
}const app = new AppComponent();
app.run(); // 输出: [LOG]: Application started
5. 元数据反射 API
TypeScript 与 reflect-metadata 库结合使用可以提供更强大的装饰器功能:
npm install reflect-metadata
然后在入口文件顶部添加:
import "reflect-metadata";
使用示例:
function logType(target: any, key: string) {const type = Reflect.getMetadata("design:type", target, key);console.log(`${key} type: ${type.name}`);
}class Demo {@logTypepublic attr1: string = "hello";@logTypepublic attr2: number = 42;
}// 输出:
// attr1 type: String
// attr2 type: Number
6. 装饰器与 Angular
Angular 框架大量使用装饰器:
import { Component, Input, Output, EventEmitter } from '@angular/core';@Component({selector: 'app-greeter',template: `<h1>Hello {{name}}!</h1><button (click)="onClick()">Click me</button>`
})
export class GreeterComponent {@Input() name: string = 'World';@Output() clicked = new EventEmitter<void>();onClick() {this.clicked.emit();}
}
7. 总结
TypeScript 装饰器提供了强大的元编程能力,可以用于:
1. 添加元数据;
2. 修改或扩展类、方法、属性的行为;
3. 实现 AOP (面向切面编程);
4. 依赖注入;
5. 验证和转换数据;
6. 日志记录和性能监控;
装饰器在 Angular、NestJS 等框架中广泛应用,是 TypeScript 高级开发的重要工具。