Skip to content

装饰器

是 TypeScript 提供的一种强大功能,它允许你通过在类声明、方法、访问器、属性或参数前加上特殊 @expression 的形式,来修改或增强类和类成员的功能。装饰器本质上是一个函数,它接收类的元数据作为参数,并可以修改或返回一个新的类定义。装饰器在运行时被调用,可以在类实例化之前修改类的行为。

基本语法

ts
@decoratorName
class MyClass {
  // ...
}

类装饰器

类装饰器在类声明之前声明,可以修改类的定义。它接收类的构造函数作为参数,并可以返回一个新的构造函数或修改原来的构造函数。

类装饰器参数

类装饰器接收两个参数:

  1. target:当前类的构造函数。
  2. context(可选,仅在 TypeScript 5.0+ 支持):提供有关装饰器调用上下文的信息,包括类的名称、修饰符等。
ts
function logClass(target: Function) {
  console.log(`Class Decorator: ${target.name} was decorated.`);
}

@logClass
class ExampleClass {}

// 输出: Class Decorator: ExampleClass was decorated.

方法装饰器

方法装饰器在方法声明之前声明,可以修改方法的定义或行为。它接收三个参数:方法所在的类的原型对象,方法名,以及方法的描述符对象。

方法装饰器参数

方法装饰器接收三个参数:

  1. target

    • 对于实例成员,这是类的原型(prototype)。
    • 对于静态成员,这是类的构造函数。
  2. propertyKey:方法的名称,作为字符串或符号传递。

  3. descriptor:属性描述符,包含方法定义的详细信息。常见的属性包括:

    • value:方法本身。
    • configurable:是否可以删除或重新定义该属性。
    • enumerable:是否可以在 for...in 循环中枚举该属性。
    • writable:是否可以更改该属性的值。
    • getgetter 方法。
    • setsetter 方法。

在 TypeScript 5.0 及以上版本中,方法装饰器还可以接收一个可选的第四个参数 context,这个参数提供了关于装饰器调用上下文的额外信息。

ts
function logMethod(target: any, key: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`Method Decorator: Calling "${key}" with`, args);
    return originalMethod.apply(this, args);
  };

  return descriptor;
}

class MyClass {
  @logMethod
  myMethod(arg1: string, arg2: number) {
    console.log(`myMethod body: ${arg1}, ${arg2}`);
  }
}

const instance = new MyClass();
instance.myMethod("Hello", 42);

// 输出:
// Method Decorator: Calling "myMethod" with ["Hello", 42]
// myMethod body: Hello, 42

属性装饰器

属性装饰器在属性声明之前声明,可以修改或观察属性的定义。它接收两个参数:类的原型对象和属性名。

属性装饰器参数

属性装饰器接收两个参数:

  1. target
    • 对于实例成员,这是类的原型(prototype)。
    • 对于静态成员,这是类的构造函数。
  2. propertyKey:属性的名称,作为字符串或符号传递。

在 TypeScript 5.0 及以上版本中,属性装饰器还可以接收一个可选的第三个参数 descriptor,这个参数提供了关于属性定义的详细信息。

ts
function logProperty(target: any, propertyName: string) {
  console.log(`Property Decorator: Property "${propertyName}" was decorated.`);
}

class MyClass {
  @logProperty
  someProperty: string;
}

// 输出: Property Decorator: Property "someProperty" was decorated.

参数装饰器

参数装饰器在方法的参数声明之前声明,可以访问或修改参数的信息。它接收三个参数:类的原型对象,方法名,以及参数在参数列表中的索引。

参数装饰器参数

参数装饰器接收三个参数:

  1. target

    • 对于实例成员,这是类的原型(prototype)。
    • 对于静态成员,这是类的构造函数。
    • 对于构造函数参数,这也是类的构造函数。
  2. propertyKey:方法的名称或构造函数的名称,作为字符串或符号传递。如果装饰的是构造函数的参数,则为 undefined

  3. parameterIndex:参数在参数列表中的位置,作为数字传递。

在 TypeScript 5.0 及以上版本中,参数装饰器还可以接收一个可选的第四个参数 context,这个参数提供了关于装饰器调用上下文的额外信息。

ts
function parameterDecorator(
  target: any,
  methodName: string,
  paramIndex: number
) {
  console.log(
    `Parameter Decorator: Parameter at index ${paramIndex} of method "${methodName}" was decorated.`
  );
}

class MyClass {
  myMethod(@parameterDecorator arg1: string, arg2: number) {
    // ...
  }
}

const instance = new MyClass();
instance.myMethod("Hello", 42);

// 输出: Parameter Decorator: Parameter at index 0 of method "myMethod" was decorated.

装饰器的使用需要在 TypeScript 编译时开启相应的实验性特性(如 --experimentalDecorators),并且在某些情况下还需要 --emitDecoratorMetadata 来支持反射元数据的生成。装饰器功能非常强大,但也需要谨慎使用,以避免过度复杂化代码结构或引入难以追踪的副作用。

使用场景

装饰器提供了一种灵活的元编程手段,使得开发者能够在不修改源代码的情况下,增强或改变类和类成员的功能,从而提高代码的可复用性、灵活性和可维护性。

  1. 日志记录和监控:可以在方法执行前后自动打印日志,或记录方法的执行时间,这对于性能监控和调试非常有用。
  2. 权限控制和验证:可以在方法调用前检查用户是否有权限执行该操作,或者验证传入参数的有效性。
  3. 注入依赖:实现依赖注入模式,自动为类或方法提供所需的依赖,这对于构建可测试和模块化的应用至关重要。
  4. 添加元数据:向类或其成员添加额外信息,这些信息可以用于自动生成文档、序列化、ORM 映射等。
  5. 修改类或方法的行为:可以在不修改原有代码的情况下,增加新的功能或改变现有功能的行为,比如添加缓存机制、事务管理等。
  6. 追踪和调试:帮助开发者理解代码的执行流程,尤其是在复杂的系统中,通过装饰器可以标记和记录关键路径。
  7. 类型检查和验证:在运行时检查属性或参数是否符合预期的类型,增强类型安全。
  8. 自动完成某些重复任务:比如自动为 RESTful API 的每个方法添加统一的错误处理、认证逻辑等。
  9. 性能优化:通过缓存装饰器减少重复计算,或者通过代理模式装饰器优化访问模式。
  10. 实现设计模式:如装饰器模式本身,可以用来动态地给对象添加责任,相比传统的继承更灵活。

装饰器工厂

装饰器工厂是一种特殊的装饰器,它接受一个参数并返回一个装饰器函数。装饰器工厂可以用来动态生成装饰器,根据不同的条件或需求来选择不同的装饰器行为。

ts
function logClass(isDev: boolean) {
  return function (target: Function) {
    console.log(`Class Decorator: ${target.name} was decorated.`);
  };
}

@logClass(true)
class ExampleClass {}

// 输出: Class Decorator: ExampleClass was decorated.

装饰器工厂可以用来动态生成装饰器,根据不同的条件或需求来选择不同的装饰器行为。

装饰器组合

装饰器可以组合使用,以实现更复杂的逻辑。装饰器组合的顺序非常重要,因为它们会按照指定的顺序应用到目标对象上。

ts
function logMethod(target: any, key: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`Method Decorator: Calling "${key}" with`, args);
    return originalMethod.apply(this, args);
  };

  return descriptor;
}

function logProperty(target: any, key: string) {
  console.log(`Property Decorator: Property "${key}" was decorated.`);
}

class MyClass {
  @logProperty
  myProperty: string;

  @logMethod
  myMethod(arg1: string, arg2: number) {
    // ...
  }
}

// 输出:
// Property Decorator: Property "myProperty" was decorated.
// Method Decorator: Calling "myMethod" with "Hello", 42

装饰器组合的顺序非常重要,因为它们会按照指定的顺序应用到目标对象上。

装饰器执行顺序

装饰器执行顺序按照以下规则进行:

  1. 参数装饰器
  2. 方法装饰器
  3. 访问符装饰器
  4. 属性装饰器
  5. 类装饰器
ts
function logClass(isDev: boolean) {
  return function (target: Function) {
    console.log(`Class Decorator: ${target.name} was decorated.`);
  };
}

function logMethod(target: any, key: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;

  descriptor.value = function (...args: any[]) {
    console.log(`Method Decorator: Calling "${key}" with`, args);
    return originalMethod.apply(this, args);
  };

  return descriptor;
}

function logProperty(target: any, key: string) {
  console.log(`Property Decorator: Property "${key}" was decorated.`);
}

@logClass(true)
class MyClass {
  @logProperty
  myProperty: string;

  @logMethod
  myMethod(arg1: string, arg2: number) {
    // ...
  }
}

// 输出:
// Property Decorator: Property "myProperty" was decorated.
// Method Decorator: Calling "myMethod" with "Hello", 42
// Class Decorator: MyClass was decorated.

Released under the MIT License.