用户工具

站点工具


typescript:第八章装饰器

第八章 装饰器

装饰器(Decorators)是TypeScript的一项实验性特性,它提供了一种以声明式方式添加注解和元编程语法的方法。装饰器可以附加到类声明、方法、访问器、属性或参数上,用于修改类的行为或添加元数据。

8.1 装饰器基础

装饰器本质上是一个函数,它在运行时被调用,被装饰的声明信息作为参数传入。要使用装饰器,必须在tsconfig.json中启用experimentalDecorators选项。

// tsconfig.json
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "target": "ES5"
  }
}

装饰器使用@expression的形式,其中expression必须是一个函数,在运行时会被调用,传入被装饰的声明信息。

8.2 装饰器分类

TypeScript支持以下几种类型的装饰器:

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

类装饰器的签名:

declare type ClassDecorator = <TFunction extends Function>(
  target: TFunction
) => TFunction | void;

如果类装饰器返回一个值,它会使用返回的构造函数替换类声明。

// 返回新构造函数的类装饰器
function classDecorator<T extends { new (...args: any[]): {} }>(
  constructor: T
) {
  return class extends constructor {
    newProperty = "new property";
    hello = "override";
  };
}
 
@classDecorator
class Greeter {
  property = "property";
  hello: string;
 
  constructor(m: string) {
    this.hello = m;
  }
}
 
console.log(new Greeter("world"));
// 输出包含newProperty和修改后的hello

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

方法装饰器的签名:

declare type MethodDecorator = <T>(
  target: Object,
  propertyKey: string | symbol,
  descriptor: TypedPropertyDescriptor<T>
) => TypedPropertyDescriptor<T> | void;

方法装饰器可以访问和修改属性描述符,控制方法的可枚举性、可配置性和可写性。

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

注意:TypeScript不允许同时装饰一个成员的get和set访问器。相反,所有装饰器必须应用于访问器的第一个文档顺序指定的访问器。

8.2.4 属性装饰器

属性装饰器声明在属性声明之前,用于添加元数据或执行其他操作。

// 属性装饰器
import "reflect-metadata";
 
const formatMetadataKey = Symbol("format");
 
function format(formatString: string) {
  return Reflect.metadata(formatMetadataKey, formatString);
}
 
function getFormat(target: any, propertyKey: string) {
  return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}
 
class Greeter {
  @format("Hello, %s")
  greeting: string;
 
  constructor(message: string) {
    this.greeting = message;
  }
 
  greet() {
    let formatString = getFormat(this, "greeting");
    return formatString.replace("%s", this.greeting);
  }
}

属性装饰器的签名:

declare type PropertyDecorator = (
  target: Object,
  propertyKey: string | symbol
) => void;

8.2.5 参数装饰器

参数装饰器声明在参数声明之前,用于监视方法参数。

// 参数装饰器
import "reflect-metadata";
 
const requiredMetadataKey = Symbol("required");
 
function required(
  target: Object,
  propertyKey: string | symbol,
  parameterIndex: number
) {
  let existingRequiredParameters: number[] = 
    Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
  existingRequiredParameters.push(parameterIndex);
  Reflect.defineMetadata(
    requiredMetadataKey,
    existingRequiredParameters,
    target,
    propertyKey
  );
}
 
function validate(
  target: any,
  propertyName: string,
  descriptor: TypedPropertyDescriptor<Function>
) {
  let method = descriptor.value!;
 
  descriptor.value = function () {
    let requiredParameters: number[] = Reflect.getOwnMetadata(
      requiredMetadataKey,
      target,
      propertyName
    );
    if (requiredParameters) {
      for (let parameterIndex of requiredParameters) {
        if (
          parameterIndex >= arguments.length ||
          arguments[parameterIndex] === undefined
        ) {
          throw new Error("Missing required argument.");
        }
      }
    }
    return method.apply(this, arguments);
  };
}
 
class Greeter {
  greeting: string;
 
  constructor(message: string) {
    this.greeting = message;
  }
 
  @validate
  greet(@required name: string) {
    return "Hello " + name + ", " + this.greeting;
  }
}

8.3 装饰器工厂

装饰器工厂是一个返回装饰器函数的函数,允许自定义装饰器的行为。

// 装饰器工厂
function log(prefix: string) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;
 
    descriptor.value = function (...args: any[]) {
      console.log(`${prefix} - Calling ${propertyKey} with:`, args);
      const result = originalMethod.apply(this, args);
      console.log(`${prefix} - Result:`, result);
      return result;
    };
  };
}
 
class Calculator {
  @log("DEBUG")
  add(a: number, b: number): number {
    return a + b;
  }
 
  @log("INFO")
  multiply(a: number, b: number): number {
    return a * b;
  }
}
 
const calc = new Calculator();
calc.add(2, 3);
calc.multiply(4, 5);

8.4 装饰器组合

多个装饰器可以应用于一个声明,按照从下到上、从右到左的顺序执行。

// 装饰器组合
function first() {
  console.log("first(): factory evaluated");
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    console.log("first(): called");
  };
}
 
function second() {
  console.log("second(): factory evaluated");
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    console.log("second(): called");
  };
}
 
class ExampleClass {
  @first()
  @second()
  method() {}
}
 
// 输出顺序:
// first(): factory evaluated
// second(): factory evaluated
// second(): called
// first(): called

8.5 元数据反射

TypeScript支持通过reflect-metadata库使用元数据反射API。

import "reflect-metadata";
 
// 定义元数据
class MyClass {
  @Reflect.metadata("custom:key", "custom value")
  myMethod() {}
}
 
// 读取元数据
const obj = new MyClass();
const metadataValue = Reflect.getMetadata("custom:key", obj, "myMethod");
console.log(metadataValue); // "custom value"
 
// 类级别的元数据
@Reflect.metadata("class:key", "class value")
class DecoratedClass {}
 
console.log(Reflect.getMetadata("class:key", DecoratedClass)); // "class value"

8.6 实际应用场景

8.6.1 日志记录装饰器

function logExecutionTime(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value;
 
  descriptor.value = function (...args: any[]) {
    const start = performance.now();
    const result = originalMethod.apply(this, args);
    const end = performance.now();
    console.log(`${propertyKey} execution time: ${end - start} milliseconds`);
    return result;
  };
}
 
class DataProcessor {
  @logExecutionTime
  processLargeDataset(data: number[]): number[] {
    // 模拟耗时操作
    return data.map(x => x * 2).filter(x => x > 10);
  }
}

8.6.2 缓存装饰器

function memoize(
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
) {
  const originalMethod = descriptor.value;
  const cache = new Map();
 
  descriptor.value = function (...args: any[]) {
    const key = JSON.stringify(args);
    if (cache.has(key)) {
      console.log("Returning cached result");
      return cache.get(key);
    }
    const result = originalMethod.apply(this, args);
    cache.set(key, result);
    return result;
  };
}
 
class Fibonacci {
  @memoize
  calculate(n: number): number {
    if (n < 2) return n;
    return this.calculate(n - 1) + this.calculate(n - 2);
  }
}

8.6.3 授权装饰器

function requireRole(role: string) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;
 
    descriptor.value = function (...args: any[]) {
      const currentUser = (this as any).currentUser;
      if (!currentUser || currentUser.role !== role) {
        throw new Error(`Access denied. Required role: ${role}`);
      }
      return originalMethod.apply(this, args);
    };
  };
}
 
class AdminPanel {
  currentUser = { role: "admin" };
 
  @requireRole("admin")
  deleteUser(userId: string) {
    console.log(`Deleting user: ${userId}`);
  }
 
  @requireRole("superadmin")
  deleteSystem() {
    console.log("Deleting system...");
  }
}

8.6.4 依赖注入装饰器

// 简单的依赖注入容器
class Container {
  private services = new Map();
 
  register(token: string, implementation: any) {
    this.services.set(token, implementation);
  }
 
  resolve(token: string) {
    return this.services.get(token);
  }
}
 
const container = new Container();
 
// 注入装饰器
function inject(token: string) {
  return function (target: any, propertyKey: string) {
    Object.defineProperty(target, propertyKey, {
      get: () => container.resolve(token),
      enumerable: true,
      configurable: true
    });
  };
}
 
// 服务定义
interface ILogger {
  log(message: string): void;
}
 
class ConsoleLogger implements ILogger {
  log(message: string) {
    console.log(`[LOG] ${message}`);
  }
}
 
// 注册服务
container.register("ILogger", new ConsoleLogger());
 
// 使用注入
class UserService {
  @inject("ILogger")
  logger!: ILogger;
 
  createUser(name: string) {
    this.logger.log(`Creating user: ${name}`);
    return { id: 1, name };
  }
}

8.7 装饰器与类型系统

装饰器可以影响类型推断,需要注意类型安全。

// 确保装饰器保持类型安全
function validateType<T>(validator: (value: any) => value is T) {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;
 
    descriptor.value = function (...args: any[]) {
      if (!validator(args[0])) {
        throw new TypeError(`Invalid argument type for ${propertyKey}`);
      }
      return originalMethod.apply(this, args);
    };
  };
}
 
// 类型守卫
function isString(value: any): value is string {
  return typeof value === "string";
}
 
class StringProcessor {
  @validateType(isString)
  process(value: string): string {
    return value.toUpperCase();
  }
}

8.8 最佳实践

1. 谨慎使用实验性功能:装饰器仍然是实验性功能,API可能会变化。

2. 保持装饰器简单:装饰器应该专注于单一职责,避免过于复杂。

3. 文档化装饰器行为:清楚地说明装饰器对类或方法的影响。

4. 考虑性能影响:装饰器在运行时增加开销,在性能敏感场景要谨慎。

5. 使用装饰器组合:通过组合小装饰器构建复杂功能,而不是创建大而全的装饰器。

6. 类型安全:确保装饰器不会破坏类型系统的完整性。

8.9 小结

装饰器是TypeScript强大的元编程工具,它允许以声明式方式修改类和成员的行为。通过合理使用类装饰器、方法装饰器、访问器装饰器、属性装饰器和参数装饰器,可以实现日志记录、缓存、验证、依赖注入等横切关注点的分离。

虽然装饰器仍是实验性功能,但它已经被许多框架(如Angular、NestJS)广泛采用。理解装饰器的工作原理和应用场景,对于掌握现代TypeScript开发至关重要。

typescript/第八章装饰器.txt · 最后更改: 127.0.0.1