跳至主要內容

装饰器

樱桃茶大约 18 分钟

装饰器

装饰器(Decorators)是 TypeScript 的一个特性,它允许你在类声明和成员上附加额外的行为。

  • 装饰器是用@expression这种形式书写的,这里的expression必须求值为一个函数,当装饰器运行时会被调用。

  • TypeScript 装饰器可以用在四个地方:

    • 类装饰器:应用于类构造函数,用来监视、修改或替换类定义。
    • 方法装饰器:应用于方法的属性描述符,可用于监听、修改或替换方法定义。
    • 访问器装饰器:应用于访问器的属性描述符,类似于方法装饰器,但它们不能同时应用于一个 getter/setter 对中的两个成员。
    • 属性装饰器:用于属性的声明,无法直接观察到属性的定义,但可以用来注册元数据。
    • 参数装饰器:应用于参数声明,用于监视、修改或替换参数的行为。
  • 这里举个类装饰器例子:

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;
  }
}

在这个例子中,sealed装饰器会封闭Greeter类,防止新属性的添加并标记所有现有属性为不可配置的。

  • 使用装饰器可能需要在 tsconfig.json 中启用实验性特性支持:
{
  "compilerOptions": {
    "experimentalDecorators": true,
    ...
  }
}
  • 装饰器工厂是一种返回装饰器函数的函数,你可以通过它来自定义装饰器如何应用到声明上。例如:
function color(value: string) {
  return function (constructor: Function) {
    // 存储颜色或做其它与颜色相关的逻辑
  };
}

@color("red")
class Car {
  // ...
}

这里的color是一个装饰器工厂,它返回一个装饰器,并给予我们机会传入参数('red')来控制装饰器的行为。

装饰器是高级 TypeScript 概念,它使得代码元编程更为易用,但它们也增加了复杂性,因此在深入理解前不建议过度使用。

装饰器

装饰器 (Decorators) 是 TypeScript 的一个高级功能,它允许你在类声明和成员上通过形如@expression这样的语法添加标注或修改类行为。装饰器可以应用到类、方法、访问器、属性和参数上。

  • 类装饰器:当你想要在类定义时添加额外的行为,例如记录日志、设置元数据等。

    function SimpleDecorator(constructor: Function) {
      console.log("SimpleDecorator called.");
    }
    
    @SimpleDecorator
    class MyClass {}
    
  • 方法装饰器:用于观察、修改或替换方法定义。

    function MethodDecorator(
      target: any,
      propertyKey: string,
      descriptor: PropertyDescriptor
    ) {
      console.log("MethodDecorator called on: ", target, propertyKey, descriptor);
    }
    
    class MyClass {
      @MethodDecorator
      method() {}
    }
    
  • 访问器装饰器:用于监控、修改或替换一个访问器(getter/setter)的定义。

    class MyClass {
      private _name: string;
    
      @AccessorDecorator
      get name() {
        return this._name;
      }
    
      set name(value: string) {
        this._name = value;
      }
    }
    
  • 属性装饰器:用于处理类中的属性;它们无法直接观察属性的初始化,但可以用来注册元数据。

    function FieldDecorator(target: any, propertyKey: string) {
      console.log("FieldDecorator called on: ", target, propertyKey);
    }
    
    class MyClass {
      @FieldDecorator
      name: string;
    }
    
  • 参数装饰器:用在参数声明前面,可以用来监控、修改或替换函数的行为。

    function ParameterDecorator(
      target: any,
      propertyKey: string | symbol,
      parameterIndex: number
    ) {
      console.log(
        "ParameterDecorator called on: ",
        target,
        propertyKey,
        parameterIndex
      );
    }
    
    class MyClass {
      method(@ParameterDecorator param1: string) {}
    }
    

注意事项:

  • 装饰器是一种实验性特性,在 TypeScript 中使用它们需要在tsconfig.json文件中启用experimentalDecorators编译器选项。

  • 装饰器执行顺序:参数装饰器 -> 方法/访问器装饰器 -> 属性装饰器 -> 类装饰器。如果有多个相同类型的装饰器,则逆序执行。

  • 装饰器工厂:如果你想配置装饰器时传入参数,可以使用装饰器工厂模式。

    function ColorDecorator(color: string) {
      return function (constructor: Function) {
        // 此处可以使用color参数来执行某些操作
      };
    }
    
    @ColorDecorator("blue")
    class MyClass {}
    
  • 装饰器可以用来扩展类的行为、拦截类的实例化过程、自定义类成员(属性/方法)的行为,以及通过第三方库提供的装饰器来集成更多功能。

装饰器工厂

装饰器工厂是一种返回装饰器函数的函数。你可以使用装饰器工厂来创建可配置的装饰器。在 TypeScript 中,装饰器通常用于为类、方法、存取器、属性或参数添加元数据、修改类的行为或者绑定方法。

  • 定义装饰器工厂: 一个函数,其内部返回一个表达装饰器逻辑的函数。

    function color(value: string) {
      // 这是装饰器工厂
      return function (target) {
        // 这是装饰器
        // 装饰逻辑放这里
      };
    }
    
  • 使用装饰器工厂: 我们调用装饰器工厂并传入所需的参数,它返回一个装饰器,然后将其应用到类、方法等上。

    @color("blue") // 使用装饰器工厂
    class Car {
      // 类的内容
    }
    
  • 装饰器执行时机: 装饰器工厂在代码运行时不会立即执行,而是在所有装饰器都解析完之后,按照特定顺序执行(从上至下依次对于属性描述符进行操作)。

  • 实用例子: 假设我们希望给类添加一个自定义的元数据标签,可以使用装饰器工厂来实现。

    // 装饰器工厂
    function metadata(key: string, value: any) {
      return function (target) {
        Reflect.defineMetadata(key, value, target);
      };
    }
    
    // 应用装饰器
    @metadata("customKey", "customValue")
    class SomeClass {
      // 类的内容
    }
    

在本例中,metadata 函数是一个装饰器工厂,它接受两个参数 keyvalue 并返回一个装饰器函数。该装饰器函数当应用于 SomeClass 类时,会利用 Reflect.defineMetadata 方法为该类添加元数据。这样的话,通过装饰器工厂,我们能够为不同的类设置不同的元数据值。

装饰器组合

装饰器(Decorator)是 TypeScript 的一个高级特性,用于在声明时对类、方法、访问器、属性或参数进行标注和修改。装饰器提供了一种声明式的语法,用于扩展和修改对象的行为,而不必更改其实际代码。

装饰器组合指的是同时应用多个装饰器到同一个声明上。当你组合装饰器时,它们的执行顺序遵循特定的规则:

  • 当多个装饰器应用于一个声明时,它们的求值方式与复合函数相似:从最后一个装饰器到第一个逐个求值。
  • 装饰器的调用按照求值的相反顺序执行,即第一个求值的装饰器最后被调用。

以下是使用无序列表解释装饰器组合的示例:

  • 装饰器工厂: 如果装饰器有额外的参数,就需要一个装饰器工厂来创建它。装饰器工厂是一个简单的函数,返回具体装饰器函数。

    function DecoratorFactory(param: string) {
      return function (target: Function) {
        // Do something with 'target' and 'param'...
      };
    }
    
  • 类装饰器组合:

    • 应用到类的多个装饰器会首先以从下到上的顺序求值。
    • 按照从上到下的顺序进行调用。
    @FirstDecorator
    @SecondDecorator
    class ExampleClass {}
    // 执行顺序 SecondDecorator -> FirstDecorator
    
  • 方法/访问器/属性/参数装饰器组合:

    • 对于方法、访问器或参数的装饰器,也同样按照上述规则进行求值与调用。
    • 不过,在这些情况下,每个装饰器接收的参数略有不同,但概念是相同的。
    class ExampleClass {
      @FirstDecorator
      @SecondDecorator
      method() {}
      // 执行顺序 SecondDecorator -> FirstDecorator
    }
    

记住,虽然装饰器提供了强大的语法来修改类的行为,但它们也增加了代码的复杂性,因此建议仅在确实需要时使用,并且要确保装饰器的作用非常清晰明了,以便于其他开发者理解和维护代码。

装饰器求值

装饰器求值(Decorator Evaluation)指的是 TypeScript 编译器在处理装饰器时的顺序和方式。装饰器是 TypeScript 提供的一个实验性语法,用于在声明期间向类、方法、访问器、属性或参数添加额外的行为。

以下是装饰器求值的基本规则:

  • 类装饰器:应用于类构造函数。

    • 对于类中的每个静态成员(包括静态属性和静态方法),从上到下依次应用装饰器。
    • 接着,对类中的每个实例成员(包括实例属性和实例方法),从上到下依次应用装饰器。
    • 最后,应用类装饰器本身。
  • 方法和访问器装饰器:应用于类的原型,不是类的实例。

    • 在对参数装饰器进行求值之前,首先对方法或访问器本身的装饰器进行求值。
  • 参数装饰器:应用于类构造函数或方法的参数。

    • 参数装饰器按其声明的顺序应用。
  • 属性装饰器:应用于类的原型。

    • 属性装饰器在运行时没有直接的反射 API 来获取关于属性的信息,因此它们会接收两个参数:
      • 目标对象:对于静态成员来说是类的构造函数,对于实例成员是类的原型。
      • 属性的名字(字符串类型)。

这些规则确保了装饰器的执行顺序是一致的,并且他们的结果可以被正确地用于类定义中。装饰器通常用于框架内部以实现依赖注入、元数据管理等高级功能。

下面举几个实用的例子:

// 类装饰器
function ClassDecorator(target: Function) {
  // 类装饰器逻辑
}

// 方法装饰器
function MethodDecorator(
  target: Object,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  // 方法装饰器逻辑
}

// 访问器装饰器
function AccessorDecorator(
  target: Object,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  // 访问器装饰器逻辑
}

// 属性装饰器
function PropertyDecorator(target: Object, propertyKey: string) {
  // 属性装饰器逻辑
}

// 参数装饰器
function ParameterDecorator(
  target: Object,
  propertyKey: string | symbol,
  parameterIndex: number
) {
  // 参数装饰器逻辑
}

@ClassDecorator
class ExampleClass {
  @PropertyDecorator
  property: string;

  constructor(
    @ParameterDecorator param1: string,
    @ParameterDecorator param2: number
  ) {}

  @MethodDecorator
  method() {}

  @AccessorDecorator
  get accessor() {
    return this.property;
  }
}

在这个例子中:

  1. 先对ExampleClass中的property属性应用PropertyDecorator
  2. 然后按顺序对构造函数的参数应用ParameterDecorator
  3. 接着对method方法应用MethodDecorator
  4. accessor访问器应用AccessorDecorator
  5. 最后,对整个ExampleClass类应用ClassDecorator

类装饰器

  • 装饰器是 TypeScript 的一种特殊类型的声明,它能够被附加到类声明、方法、访问符、属性或参数上。装饰器使用@表达式语法,后面跟着一个函数,当程序运行时,这个函数会被调用,同时被装饰的信息作为参数传入。

  • 类装饰器应用在类声明之前,用来监视、修改或替换类定义。一个类装饰器不能影响实例化之前的属性和方法,但能通过替换类的构造函数来实现对类的实例成员的监视或替换。

  • 示例:

    // 定义一个简单的类装饰器函数,打印一些信息
    function logClass(target: Function) {
      console.log(`${target.name}被初始化了`);
    }
    
    @logClass
    class MyClass {
      constructor() {
        console.log("MyClass 实例化");
      }
    }
    

    在此示例中,logClass就是一个类装饰器,当MyClass类被定义时,logClass会自动执行。装饰器函数logClass接收一个参数target,指向被装饰的类本身,在这里就是MyClass类。因此,当MyClass类定义时,控制台会显示“类 MyClass 被初始化了”。

  • 类装饰器也可以返回一个新的类,从而替换原有的类定义。这使得我们可以修改类的行为或添加额外功能。

    function replaceClass(constructor: Function) {
      return class extends (constructor as any) {
        newProperty = "new property";
        hello = "override";
      };
    }
    
    @replaceClass
    class Greeter {
      property = "property";
      hello: string;
      constructor(m: string) {
        this.hello = m;
      }
    }
    
    console.log(new Greeter("world"));
    

    在这个例子中,replaceClass装饰器创建并返回了一个新的类,这个类继承自原来的Greeter类,并添加了一个新属性newProperty以及重写了hello属性的值。因此,当创建Greeter类的实例时,实际上是创建了这个被replaceClass返回的新类的实例。

  • 使用类装饰器需要在 tsconfig.json 或命令行选项中启用experimentalDecorators选项,因为装饰器目前仍是实验性功能。

  • 类装饰器提供了一种强大的方式来进行元编程(meta-programming),即编写能够改变代码自身行为的代码,它们让类的修改变得更加灵活和动态。

方法装饰器

方法装饰器是一种特殊类型的声明,它可以被附加到类的方法上。在 TypeScript 中,装饰器提供了一种方式,让我们在不修改原有代码结构的前提下,为类方法添加额外的功能。这在很多场景下非常有用,比如日志记录、性能测试、安全控制、验证等。

  • 基本使用:方法装饰器会在运行时被调用,接受三个参数:

    • target:被装饰的方法所属的类
    • propertyKey:方法的名字
    • descriptor:方法的属性描述符(PropertyDescriptor
  • 例子:给一个类的方法添加简单的日志功能,以便在方法被调用时打印日志。

function logMethod(target: any, key: string, descriptor: PropertyDescriptor) {
  // 保存原来的方法体
  const originalMethod = descriptor.value;

  // 修改方法的定义
  descriptor.value = function (...args: any[]) {
    console.log(`Calling "${key}" with`, args);
    return originalMethod.apply(this, args);
  };

  return descriptor;
}

class Person {
  private name: string;

  constructor(name: string) {
    this.name = name;
  }

  @logMethod
  saySomething(something: string) {
    console.log(`${this.name} says: ${something}`);
  }
}

const p = new Person("John");
p.saySomething("hello"); // 控制台将打印: Calling "saySomething" with [ 'hello' ],然后是 John says: hello
  • 注意点

    • 装饰器是实验性的特性,在使用前需要在 tsconfig.json 文件中启用 experimentalDecorators 选项。
    • 方法装饰器可以修改方法的行为或替换方法的定义。
    • 如果装饰器返回一个值,它会用作方法的属性描述符。
  • 高级应用:装饰器不仅限于记录日志,还可以用于权限校验、参数校验、绑定事件监听器等复杂场景。通过合理利用装饰器,可以极大地提高代码的复用性和可维护性。

访问器装饰器

装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、访问器、属性或参数上。TypeScript 装饰器可以用来监视、修改或替换一个成员的定义。访问器装饰器是应用于类的 getter/setter 方法的。

使用访问器装饰器时需要注意以下几点:

  • 访问器装饰器是在运行时作为函数调用的,在装饰访问器的时候会得到一些描述该访问器的信息。
  • 如果装饰的是实例访问器,那么装饰器将接收三个参数:目标对象、访问器的名字和访问器的属性描述符。
  • 如果装饰的是静态访问器,那么第一个参数将是类构造函数,其他参数与实例访问器相同。
  • TypeScript 不允许同时装饰一个成员的 get 和 set 访问器,只需使用一个装饰器就可以应用到成员的所有访问器上。

下面是一些使用访问器装饰器的例子:

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;
  }
}

在这个例子中,configurable 装饰器被用于 Point 类的 xy 的 getter 访问器上。这个装饰器函数接受一个布尔值,然后返回一个新的装饰器函数,该函数被用来修改访问器的属性描述符,具体是其 configurable 属性。这里我们设置它为 false,表示这些属性不能被后期修改。

当你试图学习 TypeScript 并且已经有 JavaScript 的基础时,理解装饰器可以先从它们如何影响类的行为入手。装饰器提供了一种方式,可以在不修改对象直接代码的情况下添加额外的行为。这非常有用,比如在你想要增强类的功能,或者在维护第三方库的扩展时。

属性装饰器

属性装饰器是 TypeScript 的一个高级特性,用于在声明属性时添加额外的元数据和逻辑处理。它们不是直接影响属性值的函数,而是可以用来捕获有关类中属性的信息。

  • 装饰器本身是一个函数,它会在运行时被调用,并且能够接收两个参数:

    • target: 被装饰的属性所属的类的构造函数(对于静态属性)或者类的原型对象(对于实例属性)
    • propertyKey: 被装饰的属性的名称(字符串类型或 Symbol 类型)
  • 属性装饰器不能修改属性的值,它不像方法装饰器那样直接返回值。但是,它们可以通过反射 API(例如Reflect.metadata)用来存储元数据,或者通过其他方式间接影响类的行为。

下面是一个简单的属性装饰器示例:

function Log(target: any, propertyKey: string) {
  console.log(`Property decorator! Key: ${propertyKey}`);
}

class MyClass {
  @Log
  public name: string;

  constructor(name: string) {
    this.name = name;
  }
}

const myObj = new MyClass("Alice");

当上述代码执行时,装饰器Log会被触发,并输出"Property decorator! Key: name"到控制台。这里,装饰器没有修改任何属性值,仅仅输出了一条日志信息,并提供了被装饰属性的名称。

使用装饰器时需注意以下几点:

  • 装饰器是实验性特性,要在 TypeScript 里启用它们,需要在 tsconfig.json 中将experimentalDecorators设置为 true。
  • 由于装饰器是实验性的,它们的行为可能在未来版本的 TypeScript 中改变。
  • 装饰器可以与反射 API 结合,存储和获取关于类属性的元数据。这要求安装并引入额外的库,如reflect-metadata
  • 理解和使用装饰器需要对 JavaScript 的高级概念有一定的理解,特别是关于高阶函数和闭包。

装饰器为您在处理类和对象时提供了额外的强大工具,使得在类设计时能够更加灵活地控制和扩展功能。

参数装饰器

参数装饰器是 TypeScript 的一种高级装饰器,用于对类的参数进行注解或修改行为。以下是关于参数装饰器的具体说明:

  • 参数装饰器声明在一个参数声明之前,用来监视、修改或替换一个方法的参数定义。
  • 参数装饰器表达式会在运行时当做函数被调用,可以使用它来记录信息,或者提供额外的元数据。

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

  1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
  2. 成员的名字。
  3. 参数在函数参数列表中的索引。

记住 TypeScript 装饰器功能是实验性的,并且在未来版本可能会更改。

示例:

function Log(target: Object, methodName: string, parameterIndex: number) {
  // 该函数为参数装饰器
  console.log("Target:", target);
  console.log("methodName:", methodName);
  console.log("parameterIndex:", parameterIndex);
}

class MyClass {
  greet(@Log message: string): void {
    console.log(message);
  }
}

const myClassInstance = new MyClass();
myClassInstance.greet("Hello World");

在上面的示例中,@Log 是一个参数装饰器,应用于 greet 方法的 message 参数。每次调用 greet 方法时,参数装饰器 Log 将运行,它将打印出目标对象、方法名和参数索引。这里的目标对象是 MyClass 的原型,方法名是 "greet",而参数索引是 0 因为 message 是第一个参数。

元数据

装饰器是 TypeScript 的一个高级特性,它允许你在类声明、方法、访问器、属性或参数上添加自定义注解。元数据(metadata)是与装饰器紧密相关的一个概念,意味着我们可以在装饰器中存储或检索有关类结构的信息。

使用元数据时,TypeScript 依赖reflect-metadata库来实现这一功能。首先,确保安装了reflect-metadata库,并在项目入口点导入它。

  • 安装 reflect-metadata:

    npm install reflect-metadata
    
  • 导入 reflect-metadata:

    import "reflect-metadata";
    

以下是几个使用元数据的基础例子:

  • 为类定义元数据: 可以使用Reflect.defineMetadata方法为类定义一个元数据键和值。

    @Reflect.metadata("customDescription", "This is a class")
    class MyClass {}
    
  • 读取类元数据: 使用Reflect.getMetadata方法读取之前定义的元数据。

    const description = Reflect.getMetadata("customDescription", MyClass);
    console.log(description); // 输出: This is a class
    
  • 为方法定义元数据: 类似地,我们可以为类中的方法定义元数据。

    class AnotherClass {
      @Reflect.metadata("customMethod", "This is a method")
      myMethod() {}
    }
    
  • 读取方法元数据: 方法的元数据可以通过指定方法名作为目标来获取。

    const methodDescription = Reflect.getMetadata(
      "customMethod",
      AnotherClass.prototype,
      "myMethod"
    );
    console.log(methodDescription); // 输出: This is a method
    

通过使用装饰器和元数据,TypeScript 开发者可以编写更具表达力和灵活性的代码,尤其是在设计复杂的框架或库时。装饰器结合元数据使得在运行时对类的行为进行注释和修改成为可能,为开发者提供了强大的工具来增强和简化代码管理。