TypeScript装饰器入门:基础概念与简单示例
什么是 TypeScript 装饰器
TypeScript 装饰器是一种特殊类型的声明,它能够附加到类声明、方法、属性或参数上,为它们添加额外的行为或元数据。装饰器本质上是一个函数,这个函数会在被装饰的目标(类、方法等)被定义时执行,并且可以对目标进行修改。
装饰器在很多场景下都非常有用,例如日志记录、权限验证、性能测量等。通过使用装饰器,可以将这些横切关注点从业务逻辑中分离出来,提高代码的可维护性和可复用性。
装饰器的类型
- 类装饰器:应用于类的声明,用于修改类的定义。
- 方法装饰器:应用于类的方法,可用于修改方法的属性描述符或添加额外的行为。
- 属性装饰器:应用于类的属性,可用于添加元数据或修改属性的特性。
- 参数装饰器:应用于类方法的参数,可用于收集关于参数的信息。
基础语法
装饰器的语法使用 @
符号加上装饰器函数名,紧跟在被装饰的目标之前。例如:
@logClass
class MyClass {
@logMethod
myMethod(@logParameter param: string) {
console.log(`Method called with parameter: ${param}`);
}
}
在上述代码中,@logClass
是类装饰器,@logMethod
是方法装饰器,@logParameter
是参数装饰器。
类装饰器
类装饰器是应用于类声明的装饰器。它接收一个参数,即被装饰类的构造函数。通过这个构造函数,我们可以修改类的行为或添加新的属性和方法。
示例:简单的类装饰器
function logClass(target: Function) {
console.log(`Class ${target.name} has been decorated`);
}
@logClass
class MyClass {
message: string;
constructor(message: string) {
this.message = message;
}
printMessage() {
console.log(this.message);
}
}
在上述示例中,logClass
是一个简单的类装饰器。当 MyClass
被定义时,logClass
函数会被执行,输出 Class MyClass has been decorated
。
示例:增强类的功能
function enhanceClass(target: Function) {
return class extends target {
newMethod() {
console.log('This is a new method added by the decorator');
}
};
}
@enhanceClass
class MyBaseClass {
baseMethod() {
console.log('This is a base method');
}
}
let instance = new MyBaseClass();
instance.baseMethod();
// 这里会报错,因为在原类中没有 newMethod
// instance.newMethod();
// 如果想使用 newMethod,需要这样做
let enhancedInstance = new (enhanceClass(MyBaseClass))();
enhancedInstance.newMethod();
在这个示例中,enhanceClass
装饰器返回一个新的类,这个类继承自被装饰的类,并添加了一个新的方法 newMethod
。
方法装饰器
方法装饰器应用于类的方法。它接收三个参数:
- 目标对象:对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象。
- 方法名:被装饰方法的名称。
- 属性描述符:方法的属性描述符,通过它可以修改方法的行为,如
value
、writable
、enumerable
和configurable
等。
示例:记录方法调用日志
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling method ${propertyKey} with arguments:`, args);
const result = originalMethod.apply(this, args);
console.log(`Method ${propertyKey} returned:`, result);
return result;
};
return descriptor;
}
class MathOperations {
@logMethod
add(a: number, b: number) {
return a + b;
}
}
let mathOps = new MathOperations();
mathOps.add(2, 3);
在上述示例中,logMethod
装饰器记录了方法调用的参数和返回值。它首先保存原始方法,然后重新定义 descriptor.value
,在调用原始方法前后添加日志记录。
示例:改变方法的可枚举性
function makeMethodNonEnumerable(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.enumerable = false;
return descriptor;
}
class MyClassWithMethod {
@makeMethodNonEnumerable
myMethod() {
console.log('This is my method');
}
}
let myObj = new MyClassWithMethod();
for (let prop in myObj) {
if (myObj.hasOwnProperty(prop)) {
console.log(prop);
}
}
// 这里不会输出 myMethod,因为它已经不可枚举
在这个示例中,makeMethodNonEnumerable
装饰器将方法的 enumerable
属性设置为 false
,使得该方法在使用 for...in
循环遍历时不会被列出。
属性装饰器
属性装饰器应用于类的属性。它接收两个参数:
- 目标对象:对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象。
- 属性名:被装饰属性的名称。
属性装饰器通常用于添加元数据,而不是直接修改属性的行为。
示例:添加属性元数据
function addMetadata(target: any, propertyKey: string) {
Reflect.defineMetadata('description', `This is the ${propertyKey} property`, target, propertyKey);
}
class MyMetadataClass {
@addMetadata
myProperty: string;
constructor(value: string) {
this.myProperty = value;
}
}
let metadataObj = new MyMetadataClass('Some value');
let description = Reflect.getMetadata('description', metadataObj,'myProperty');
console.log(description);
在上述示例中,addMetadata
装饰器使用 Reflect.defineMetadata
方法为 myProperty
属性添加了一个描述元数据。然后通过 Reflect.getMetadata
方法获取该元数据并输出。
参数装饰器
参数装饰器应用于类方法的参数。它接收三个参数:
- 目标对象:对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象。
- 方法名:被装饰方法的名称。
- 参数索引:参数在方法参数列表中的位置索引。
参数装饰器通常用于收集关于参数的信息,例如验证参数类型或记录参数的使用情况。
示例:记录参数使用
function logParameter(target: any, propertyKey: string, parameterIndex: number) {
let existingLog = Reflect.getMetadata('parameterUsage', target, propertyKey) || [];
existingLog.push(`Parameter at index ${parameterIndex} has been used`);
Reflect.defineMetadata('parameterUsage', existingLog, target, propertyKey);
}
class ParameterLoggerClass {
logMessage(@logParameter message: string) {
console.log(`Message: ${message}`);
}
}
let loggerObj = new ParameterLoggerClass();
loggerObj.logMessage('Hello, world!');
let usageLog = Reflect.getMetadata('parameterUsage', loggerObj, 'logMessage');
console.log(usageLog);
在这个示例中,logParameter
装饰器记录了方法参数的使用情况。它使用 Reflect.getMetadata
和 Reflect.defineMetadata
方法来管理和存储参数使用的日志信息。
装饰器工厂
装饰器工厂是一个返回装饰器函数的函数。它允许我们在定义装饰器时传入参数,从而创建不同配置的装饰器。
示例:带有参数的日志装饰器
function logWithPrefix(prefix: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`${prefix} Calling method ${propertyKey} with arguments:`, args);
const result = originalMethod.apply(this, args);
console.log(`${prefix} Method ${propertyKey} returned:`, result);
return result;
};
return descriptor;
};
}
class PrefixLoggerClass {
@logWithPrefix('DEBUG: ')
debugMethod(a: number, b: number) {
return a + b;
}
@logWithPrefix('INFO: ')
infoMethod(a: number, b: number) {
return a * b;
}
}
let prefixLogger = new PrefixLoggerClass();
prefixLogger.debugMethod(2, 3);
prefixLogger.infoMethod(4, 5);
在上述示例中,logWithPrefix
是一个装饰器工厂。它接收一个 prefix
参数,并返回一个装饰器函数。通过这个工厂,我们可以创建不同前缀的日志装饰器,如 DEBUG:
和 INFO:
。
装饰器的执行顺序
- 参数装饰器:按照参数从左到右的顺序执行。
- 方法装饰器:在参数装饰器之后执行。
- 属性装饰器:在方法装饰器之后执行。
- 类装饰器:最后执行。
示例:验证执行顺序
function paramDecorator(target: any, propertyKey: string, parameterIndex: number) {
console.log(`Parameter decorator at index ${parameterIndex}`);
}
function methodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(`Method decorator for ${propertyKey}`);
}
function propertyDecorator(target: any, propertyKey: string) {
console.log(`Property decorator for ${propertyKey}`);
}
function classDecorator(target: Function) {
console.log(`Class decorator for ${target.name}`);
}
class ExecutionOrderClass {
@propertyDecorator
myProperty: string;
@methodDecorator
myMethod(@paramDecorator param: string) {
console.log(`Method called with parameter: ${param}`);
}
}
@classDecorator
class OuterExecutionOrderClass extends ExecutionOrderClass { }
在上述示例中,运行代码时会按照参数装饰器、方法装饰器、属性装饰器、类装饰器的顺序输出日志信息,验证了装饰器的执行顺序。
装饰器与 ES 标准
目前,装饰器还处于 ECMAScript 标准的提案阶段,尚未完全标准化。TypeScript 对装饰器的支持是基于实验性的实现。在使用装饰器时,需要注意以下几点:
- 编译选项:在 TypeScript 编译时,需要启用
experimentalDecorators
选项,例如在tsconfig.json
中添加"experimentalDecorators": true
。 - 兼容性:由于装饰器不是标准的 JavaScript 特性,在运行时可能需要使用一些转译工具(如 Babel)来确保在不同环境中正常工作。
总结
TypeScript 装饰器为我们提供了一种强大的方式来为类、方法、属性和参数添加额外的行为和元数据。通过合理使用装饰器,可以使代码更加模块化、可维护和可复用。然而,由于装饰器的实验性,在实际项目中使用时需要谨慎,并充分考虑兼容性和未来标准的变化。在日常开发中,可以根据具体需求,灵活运用类装饰器、方法装饰器、属性装饰器和参数装饰器,以及装饰器工厂,来优化代码结构和提高开发效率。希望通过本文的介绍和示例,你对 TypeScript 装饰器有了更深入的理解,并能够在自己的项目中合理地运用它们。