在 TypeScript 中,装饰器(Decorators)是一种特殊类型的声明,它可以被附加到类声明、方法、属性或参数上,用来修改类的行为。装饰器提供了一种方式来在不修改原有代码结构的基础上,对类及其成员进行扩展和增强。下面将从基本概念、装饰器类型、装饰器工厂、元数据等方面详细介绍 TypeScript 装饰器。
基本概念
装饰器本质上是一个函数,它接收不同数量的参数,具体取决于它所装饰的目标类型。装饰器可以修改目标的行为,例如添加额外的属性、修改方法的实现等。在 TypeScript 中使用装饰器需要在 tsconfig.json
中开启 experimentalDecorators
选项:
json
{
"compilerOptions": {
"experimentalDecorators": true
}
}
装饰器类型
1. 类装饰器
类装饰器应用于类的构造函数,可用于修改类的构造函数或类的原型。类装饰器接收一个参数,即类的构造函数。
typescript
function logClass(constructor: Function) {
console.log(`Class ${constructor.name} is created.`);
}
@logClass
class MyClass {
constructor() {}
}
在上述代码中,logClass
是一个类装饰器,它会在 MyClass
类创建时输出一条日志。
2. 方法装饰器
方法装饰器应用于类的方法,接收三个参数:目标对象、方法名和属性描述符。可以用于修改方法的行为,例如添加日志、验证输入等。
typescript
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling method ${propertyKey} with arguments: ${JSON.stringify(args)}`);
const result = originalMethod.apply(this, args);
console.log(`Method ${propertyKey} returned: ${result}`);
return result;
};
return descriptor;
}
class Calculator {
@logMethod
add(a: number, b: number) {
return a + b;
}
}
const calc = new Calculator();
calc.add(1, 2);
在上述代码中,logMethod
是一个方法装饰器,它会在调用 add
方法前后输出日志。
3. 属性装饰器
属性装饰器应用于类的属性,接收两个参数:目标对象和属性名。属性装饰器主要用于记录属性的元数据或修改属性的行为。
typescript
function readonly(target: any, propertyKey: string) {
const descriptor: PropertyDescriptor = {
writable: false
};
Object.defineProperty(target, propertyKey, descriptor);
}
class Person {
@readonly
name: string = 'John';
}
const person = new Person();
// 下面这行代码会报错,因为 name 属性是只读的
// person.name = 'Jane';
在上述代码中,readonly
是一个属性装饰器,它将 name
属性设置为只读。
4. 参数装饰器
参数装饰器应用于类方法的参数,接收三个参数:目标对象、方法名和参数索引。参数装饰器主要用于记录参数的元数据。
typescript
function logParameter(target: any, propertyKey: string, parameterIndex: number) {
console.log(`Parameter at index ${parameterIndex} of method ${propertyKey} is logged.`);
}
class Example {
method(@logParameter param: string) {}
}
const example = new Example();
example.method('test');
在上述代码中,logParameter
是一个参数装饰器,它会在调用 method
方法时输出参数的索引信息。
装饰器工厂
装饰器工厂是一个函数,它返回一个装饰器。通过装饰器工厂,可以在使用装饰器时传递参数。
typescript
function logWithMessage(message: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`${message} - Calling method ${propertyKey} with arguments: ${JSON.stringify(args)}`);
const result = originalMethod.apply(this, args);
console.log(`${message} - Method ${propertyKey} returned: ${result}`);
return result;
};
return descriptor;
};
}
class AnotherCalculator {
@logWithMessage('Custom log message')
multiply(a: number, b: number) {
return a * b;
}
}
const anotherCalc = new AnotherCalculator();
anotherCalc.multiply(2, 3);
在上述代码中,logWithMessage
是一个装饰器工厂,它接收一个参数 message
,并返回一个方法装饰器。
装饰器执行顺序
- 多个装饰器应用于同一个目标时:装饰器的执行顺序是从下往上(从靠近目标的装饰器开始执行),从内往外(如果有装饰器工厂,先执行装饰器工厂返回的装饰器)。
typescript
function decorator1() {
console.log('Decorator 1 factory');
return function (target: any) {
console.log('Decorator 1');
};
}
function decorator2() {
console.log('Decorator 2 factory');
return function (target: any) {
console.log('Decorator 2');
};
}
@decorator1()
@decorator2()
class MyClass2 {}
上述代码的输出顺序为:
dart
Decorator 2 factory
Decorator 1 factory
Decorator 2
Decorator 1
元数据
在 TypeScript 中,可以使用 reflect-metadata
库来处理装饰器的元数据。元数据可以用于存储和获取与类、方法、属性或参数相关的额外信息。
typescript
import 'reflect-metadata';
const METADATA_KEY = 'exampleMetadata';
function addMetadata(value: any) {
return function (target: any, propertyKey: string) {
Reflect.defineMetadata(METADATA_KEY, value, target, propertyKey);
};
}
function getMetadata(target: any, propertyKey: string) {
return Reflect.getMetadata(METADATA_KEY, target, propertyKey);
}
class MyClass3 {
@addMetadata('Some metadata')
myProperty: string;
}
const instance = new MyClass3();
const metadata = getMetadata(instance, 'myProperty');
console.log(metadata); // 输出: Some metadata
在上述代码中,使用 reflect-metadata
库的 Reflect.defineMetadata
和 Reflect.getMetadata
方法来定义和获取元数据。
总结
TypeScript 装饰器提供了一种强大的方式来扩展和修改类及其成员的行为。通过不同类型的装饰器和装饰器工厂,可以实现各种功能,如日志记录、权限验证、只读属性等。同时,结合 reflect-metadata
库可以处理装饰器的元数据,进一步增强装饰器的功能。