Skip to content
On this page

TypeScript 装饰器

装饰器是 TypeScript 中的高级特性,允许我们以声明的方式修改类和类成员。装饰器使用 @expression 语法,其中 expression 必须计算为一个函数,在运行时调用。

装饰器基础

启用装饰器

首先需要在 tsconfig.json 中启用装饰器:

json
{
  "compilerOptions": {
    "target": "ES5",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

装饰器工厂

typescript
// 装饰器工厂函数
function color(value: string) { // 装饰器工厂
  return function (target) { // 装饰器函数
    // 操作 target
  }
}

类装饰器

类装饰器应用于类的构造函数,用于观察、修改或替换类定义。

typescript
// 基本类装饰器
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;
  }
}

// 带参数的类装饰器
function reportable<T extends { new(...args: any[]): {} }>(constructor: T) {
  return class extends constructor {
    reportingURL = "http://www...";
  };
}

@reportable
class Particles {
  constructor(public position: number) {}
}

方法装饰器

方法装饰器应用于方法的属性描述符,用于观察、修改或替换方法定义。

typescript
// 方法装饰器定义
function enumerable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.enumerable = value;
  };
}

class Greeter2 {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }

  @enumerable(false)
  greet() {
    return "Hello, " + this.greeting;
  }
}

// 日志方法装饰器
function log(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  
  descriptor.value = function (...args: any[]) {
    console.log(`Calling ${propertyKey} with`, args);
    const result = originalMethod.apply(this, args);
    console.log(`${propertyKey} returned`, result);
    return result;
  };
}

class Calculator {
  @log
  add(x: number, y: number): number {
    return x + y;
  }
}

访问器装饰器

访问器装饰器应用于访问器的属性描述符,用于观察、修改或替换访问器定义。

typescript
// 访问器装饰器
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
// 属性装饰器
function format(target: Object, propertyKey: string) {
  // 为属性创建元数据
  Reflect.defineMetadata('format', 'default', target, propertyKey);
}

class Greeter3 {
  @format
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
}

// 更实用的属性装饰器示例
function minLength(length: number) {
  return function(target: any, propertyKey: string) {
    let value: string;

    const getter = function() {
      return value;
    };

    const setter = function(newVal: string) {
      if (newVal.length < length) {
        console.log(`Error: Length must be at least ${length} characters`);
      } else {
        value = newVal;
      }
    };

    Object.defineProperty(target, propertyKey, {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true
    });
  };
}

class User {
  @minLength(6)
  password: string;

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

参数装饰器

参数装饰器应用于方法参数,接收三个参数:构造函数、方法名和参数索引。

typescript
// 参数装饰器
function validate(target: Object, methodName: string, paramIndex: number) {
  console.log(`Validating parameter at index ${paramIndex} for method ${methodName}`);
}

class Person {
  name: string;

  setName(@validate name: string) {
    this.name = name;
  }
}

// 更实用的参数装饰器示例
function inject(serviceName: string) {
  return function(target: Object, methodName: string | symbol, paramIndex: number) {
    // 存储依赖注入信息
    const existingRequiredServices = Reflect.getMetadata('requiredServices', target, methodName) || [];
    existingRequiredServices[paramIndex] = serviceName;
    Reflect.defineMetadata('requiredServices', existingRequiredServices, target, methodName);
  };
}

class UserService {
  getUser(@inject('httpService') httpService: any, @inject('configService') configService: any) {
    // 方法实现
  }
}

高级装饰器模式

路由装饰器(类似 Express 或 Koa)

typescript
// 模拟路由装饰器
const routes: { path: string; method: string; handler: Function }[] = [];

function Route(path: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET') {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    routes.push({
      path,
      method,
      handler: descriptor.value
    });
  };
}

class UserController {
  @Route('/users', 'GET')
  getUsers() {
    return [{ id: 1, name: 'John' }];
  }

  @Route('/users/:id', 'GET')
  getUser() {
    return { id: 1, name: 'John' };
  }

  @Route('/users', 'POST')
  createUser() {
    return { id: 2, name: 'Jane' };
  }
}

依赖注入装饰器

typescript
// 简单的依赖注入系统
const injectableRegistry = new Map();

function Injectable() {
  return function (constructor: Function) {
    injectableRegistry.set(constructor.name, constructor);
  };
}

function Inject(token: string) {
  return function (target: any, propertyKey: string | symbol, parameterIndex: number) {
    // 为参数存储注入标记
    const existingTokens = Reflect.getMetadata('inject:tokens', target, propertyKey) || [];
    existingTokens[parameterIndex] = token;
    Reflect.defineMetadata('inject:tokens', existingTokens, target, propertyKey);
  };
}

@Injectable()
class DatabaseService {
  connect() {
    console.log('Connected to database');
  }
}

@Injectable()
class UserService {
  constructor(@Inject('DatabaseService') private db: DatabaseService) {}

  getUser(id: number) {
    this.db.connect();
    return { id, name: 'John' };
  }
}

性能监控装饰器

typescript
function Performance(target: Object, 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} executed in ${end - start} milliseconds`);
    return result;
  };

  return descriptor;
}

class DataProcessor {
  @Performance
  processData(data: number[]): number[] {
    return data.map(n => n * 2);
  }
}

装饰器元数据

需要安装 reflect-metadata 包来使用装饰器元数据:

bash
npm install reflect-metadata
typescript
import 'reflect-metadata';

function Metadata(key: string, value: any) {
  return function(target: Object, propertyKey: string | symbol) {
    Reflect.defineMetadata(key, value, target, propertyKey);
  };
}

class ExampleClass {
  @Metadata('description', 'This is a sample property')
  sampleProperty: string;

  @Metadata('version', '1.0.0')
  sampleMethod() {
    // 方法实现
  }
}

// 获取元数据
const description = Reflect.getMetadata('description', ExampleClass.prototype, 'sampleProperty');
console.log(description); // 输出: This is a sample property

装饰器实际应用

NestJS 风格的控制器装饰器

typescript
// 模拟 NestJS 风格的装饰器
function Controller(path: string) {
  return function (constructor: Function) {
    Reflect.defineMetadata('controller:path', path, constructor);
  };
}

function Get(path: string = '') {
  return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
    Reflect.defineMetadata('route:path', path, target, propertyKey);
    Reflect.defineMetadata('route:method', 'GET', target, propertyKey);
  };
}

function Post(path: string = '') {
  return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
    Reflect.defineMetadata('route:path', path, target, propertyKey);
    Reflect.defineMetadata('route:method', 'POST', target, propertyKey);
  };
}

@Controller('/users')
class UsersController {
  @Get('/')
  findAll() {
    return [];
  }

  @Get('/:id')
  findOne() {
    return {};
  }

  @Post('/')
  create() {
    return {};
  }
}

小结

装饰器是 TypeScript 中一个强大的特性,允许我们以声明的方式修改类和类成员。通过装饰器,我们可以实现横切关注点(如日志、验证、授权等),使代码更加模块化和可重用。虽然装饰器目前仍处于提案阶段,但它们在许多框架(如 Angular、NestJS)中得到了广泛应用。