TypeScript 装饰器工厂详解

78 阅读3分钟

TypeScript 装饰器工厂详解

目录

  1. 基本概念
  2. 基本语法
  3. 参数类型
  4. 返回值处理
  5. 常见用途
  6. 实际应用
  7. 最佳实践

基本概念

什么是装饰器工厂?

装饰器工厂是一个返回装饰器的函数,它可以接受参数来自定义装饰器的行为。通过装饰器工厂,我们可以创建更灵活和可配置的装饰器。函数能返回装饰器,就叫装饰器工厂

为什么使用装饰器工厂?

  1. 可以传递参数来配置装饰器
  2. 提供更大的灵活性
  3. 可以根据参数动态生成装饰器
  4. 支持复杂的装饰器逻辑

基本语法

1. 装饰器工厂基本结构

// 基本结构
function decoratorFactory(config: any) {
  // 返回实际的装饰器函数
  return function(target: any, propertyKey?: string, descriptor?: PropertyDescriptor) {
    // 装饰器逻辑
  };
}

// 使用装饰器工厂
@decoratorFactory({
  // 配置参数
})
class Example {
  // 类的内容
}

参数类型

1. 基本参数类型

// 单个参数
function Logger(prefix: string) {
  return function(target: Function) {
    // 使用 prefix 参数
    console.log(`${prefix}: ${target.name}`);
  };
}

// 多个参数
function Configure(id: string, options: { debug?: boolean; timeout?: number }) {
  return function(target: Function) {
    // 使用多个参数
    console.log(`Configuring ${id} with`, options);
  };
}

// 使用
@Logger('MyClass')
@Configure('component-1', { debug: true, timeout: 3000 })
class Example {}

2. 泛型参数

// 泛型装饰器工厂
function TypedDecorator<T>(config: T) {
  return function<U extends { new(...args: any[]): {} }>(constructor: U) {
    return class extends constructor {
      _config: T = config;
    };
  };
}

interface Config {
  apiUrl: string;
  timeout: number;
}

@TypedDecorator<Config>({
  apiUrl: 'https://api.example.com',
  timeout: 5000
})
class ApiClient {}

返回值处理

1. 类装饰器返回值

function WithTemplate(template: string, hookId: string) {
  return function<T extends { new(...args: any[]): { name: string } }>(
    originalConstructor: T
  ) {
    // 返回新的类
    return class extends originalConstructor {
      constructor(...args: any[]) {
        super(...args);
        const hookEl = document.getElementById(hookId);
        if (hookEl) {
          hookEl.innerHTML = template;
          hookEl.querySelector('h1')!.textContent = this.name;
        }
      }
    };
  };
}

@WithTemplate('<h1>My Person Object</h1>', 'app')
class Person {
  name = 'Max';
}

2. 方法装饰器返回值

function Measure(units: string = 'ms') {
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ): PropertyDescriptor {
    const originalMethod = descriptor.value;

    // 返回新的属性描述符
    return {
      ...descriptor,
      value: function(...args: any[]) {
        const start = performance.now();
        const result = originalMethod.apply(this, args);
        const finish = performance.now();
        console.log(`${propertyKey} took ${finish - start}${units}`);
        return result;
      }
    };
  };
}

class Example {
  @Measure('ms')
  calculateSomething(iterations: number) {
    // 复杂计算...
  }
}

3. 属性装饰器返回值

function Format(formatString: string) {
  return function(target: any, propertyKey: string): any {
    // 属性描述符
    let value: any;
    
    // 返回属性描述符
    return {
      get() {
        return `${formatString}: ${value}`;
      },
      set(newValue: any) {
        value = newValue;
      },
      enumerable: true,
      configurable: true
    };
  };
}

class User {
  @Format('ID')
  id: string = '123';
}

常见用途

1. 验证装饰器

function Validate(validationFn: (value: any) => boolean, errorMessage: string) {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = function(...args: any[]) {
      if (!validationFn(args[0])) {
        throw new Error(`Validation Error: ${errorMessage}`);
      }
      return originalMethod.apply(this, args);
    };

    return descriptor;
  };
}

class UserService {
  @Validate(
    (age: number) => age >= 0 && age <= 120,
    'Age must be between 0 and 120'
  )
  setAge(age: number) {
    this.age = age;
  }
}

2. 缓存装饰器

function Memoize(hashFunction?: (...args: any[]) => string) {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;
    const cache = new Map<string, any>();

    descriptor.value = function(...args: any[]) {
      const key = hashFunction ? hashFunction(...args) : JSON.stringify(args);
      if (cache.has(key)) {
        return cache.get(key);
      }
      const result = originalMethod.apply(this, args);
      cache.set(key, result);
      return result;
    };

    return descriptor;
  };
}

class MathUtils {
  @Memoize()
  fibonacci(n: number): number {
    if (n <= 1) return n;
    return this.fibonacci(n - 1) + this.fibonacci(n - 2);
  }
}

3. 日志装饰器

function Log(options: { 
  level?: 'debug' | 'info' | 'warn' | 'error';
  prefix?: string;
}) {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;
    const { level = 'info', prefix = '' } = options;

    descriptor.value = function(...args: any[]) {
      console[level](`${prefix}[${propertyKey}] Called with:`, args);
      const result = originalMethod.apply(this, args);
      console[level](`${prefix}[${propertyKey}] Returned:`, result);
      return result;
    };

    return descriptor;
  };
}

class Service {
  @Log({ level: 'debug', prefix: 'Service' })
  getData(id: string) {
    return { id, timestamp: Date.now() };
  }
}

实际应用

1. API 请求装饰器

interface RequestConfig {
  method: 'GET' | 'POST' | 'PUT' | 'DELETE';
  url: string;
  headers?: Record<string, string>;
}

function Request(config: RequestConfig) {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;

    descriptor.value = async function(...args: any[]) {
      try {
        const response = await fetch(config.url, {
          method: config.method,
          headers: {
            'Content-Type': 'application/json',
            ...config.headers
          },
          body: config.method !== 'GET' ? JSON.stringify(args[0]) : undefined
        });

        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }

        const data = await response.json();
        return originalMethod.call(this, data);
      } catch (error) {
        console.error(`Request failed:`, error);
        throw error;
      }
    };

    return descriptor;
  };
}

class UserApi {
  @Request({
    method: 'GET',
    url: 'https://api.example.com/users'
  })
  async getUsers(data: any) {
    return data.map((user: any) => ({
      ...user,
      fullName: `${user.firstName} ${user.lastName}`
    }));
  }
}

2. 权限控制装饰器

interface Role {
  name: string;
  permissions: string[];
}

function RequirePermission(permission: string) {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;

    descriptor.value = function(...args: any[]) {
      const user = getCurrentUser();
      const hasPermission = user.roles.some(
        (role: Role) => role.permissions.includes(permission)
      );

      if (!hasPermission) {
        throw new Error(`Permission denied: ${permission} is required`);
      }

      return originalMethod.apply(this, args);
    };

    return descriptor;
  };
}

class AdminPanel {
  @RequirePermission('user.delete')
  deleteUser(userId: string) {
    // 删除用户逻辑
  }

  @RequirePermission('user.edit')
  updateUser(userId: string, data: any) {
    // 更新用户逻辑
  }
}

最佳实践

1. 类型安全

// 使用泛型和接口确保类型安全
interface DecoratorConfig<T> {
  validate?: (value: T) => boolean;
  transform?: (value: T) => T;
  errorMessage?: string;
}

function TypeSafeDecorator<T>(config: DecoratorConfig<T>) {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    // 实现类型安全的装饰器逻辑
  };
}

2. 错误处理

function SafeDecorator(errorHandler: (error: Error) => void) {
  return function(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;

    descriptor.value = function(...args: any[]) {
      try {
        return originalMethod.apply(this, args);
      } catch (error) {
        errorHandler(error as Error);
        throw error;
      }
    };

    return descriptor;
  };
}

3. 组合装饰器

function compose(...decorators: Function[]) {
  return function(target: any, key: string, descriptor: PropertyDescriptor) {
    return decorators.reduce((acc, decorator) => {
      return decorator(target, key, acc);
    }, descriptor);
  };
}

class Example {
  @compose(
    Log({ level: 'debug' }),
    Validate(value => value > 0),
    Memoize()
  )
  calculate(value: number) {
    // 计算逻辑
  }
}

总结

  1. 装饰器工厂的优点:

    • 可配置性强
    • 代码复用
    • 类型安全
    • 灵活性高
  2. 使用场景:

    • 日志记录
    • 性能监控
    • 权限控制
    • 数据验证
    • 缓存处理
    • API 请求处理
  3. 最佳实践:

    • 保持简单
    • 类型安全
    • 错误处理
    • 可组合性
    • 参数验证
    • 返回值处理