TypeScript 装饰器与元数据

这一篇深入 TypeScript 的装饰器系统:从装饰器的基本概念,
到各种装饰器的使用,再到元数据反射 API。

装饰器是一种特殊类型的声明,可以附加到类、方法、属性、参数上。

tsconfig.json 中启用装饰器支持:

1{
2  "compilerOptions": {
3    "experimentalDecorators": true,
4    "emitDecoratorMetadata": true
5  }
6}

装饰器可以是普通函数或装饰器工厂:

 1// 普通装饰器
 2function sealed(target: any) {
 3  Object.seal(target);
 4}
 5
 6// 装饰器工厂
 7function color(value: string) {
 8  return function(target: any) {
 9    // 使用 value
10  };
11}

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

 1function sealed(constructor: Function) {
 2  Object.seal(constructor);
 3  Object.seal(constructor.prototype);
 4}
 5
 6@sealed
 7class BugReport {
 8  type = 'report';
 9  title: string;
10
11  constructor(t: string) {
12    this.title = t;
13  }
14}
 1function reportableClassDecorator<T extends { new (...args: any[]): {} }>(
 2  constructor: T
 3) {
 4  return class extends constructor {
 5    reportingURL = 'http://www...';
 6  };
 7}
 8
 9@reportableClassDecorator
10class BugReport {
11  type = 'report';
12  title: string;
13
14  constructor(t: string) {
15    this.title = t;
16  }
17}
18
19const bug = new BugReport('Needs dark mode');
20console.log(bug.reportingURL); // 'http://www...'
 1// 服务装饰器
 2function Injectable() {
 3  return function<T extends { new (...args: any[]): {} }>(constructor: T) {
 4    return class extends constructor {
 5      // 可以在这里添加依赖注入逻辑
 6    };
 7  };
 8}
 9
10@Injectable()
11class UserService {
12  getUsers() {
13    return ['John', 'Jane'];
14  }
15}

方法装饰器应用于方法的属性描述符。

 1function enumerable(value: boolean) {
 2  return function(
 3    target: any,
 4    propertyKey: string,
 5    descriptor: PropertyDescriptor
 6  ) {
 7    descriptor.enumerable = value;
 8  };
 9}
10
11class Greeter {
12  greeting: string;
13  constructor(message: string) {
14    this.greeting = message;
15  }
16
17  @enumerable(false)
18  greet() {
19    return 'Hello, ' + this.greeting;
20  }
21}
 1function log(target: any, propertyName: string, descriptor: PropertyDescriptor) {
 2  const method = descriptor.value;
 3
 4  descriptor.value = function(...args: any[]) {
 5    console.log(`Calling ${propertyName} with args:`, args);
 6    const result = method.apply(this, args);
 7    console.log(`Result:`, result);
 8    return result;
 9  };
10}
11
12class Calculator {
13  @log
14  add(a: number, b: number): number {
15    return a + b;
16  }
17}
18
19const calc = new Calculator();
20calc.add(1, 2); // 输出日志
 1function measure(target: any, propertyName: string, descriptor: PropertyDescriptor) {
 2  const method = descriptor.value;
 3
 4  descriptor.value = function(...args: any[]) {
 5    const start = performance.now();
 6    const result = method.apply(this, args);
 7    const end = performance.now();
 8    console.log(`${propertyName} took ${end - start} milliseconds`);
 9    return result;
10  };
11}
12
13class DataProcessor {
14  @measure
15  processData(data: any[]) {
16    // 处理数据
17    return data.map(x => x * 2);
18  }
19}

属性装饰器应用于类的属性。

 1function format(formatString: string) {
 2  return function(target: any, propertyKey: string) {
 3    // 可以在这里存储元数据
 4    Reflect.defineMetadata('format', formatString, target, propertyKey);
 5  };
 6}
 7
 8class User {
 9  @format('uppercase')
10  name: string;
11
12  @format('lowercase')
13  email: string;
14}
 1function required(target: any, propertyKey: string) {
 2  const existingRequired = Reflect.getMetadata('required', target) || [];
 3  existingRequired.push(propertyKey);
 4  Reflect.defineMetadata('required', existingRequired, target);
 5}
 6
 7function validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
 8  const method = descriptor.value;
 9  const required = Reflect.getMetadata('required', target) || [];
10
11  descriptor.value = function(...args: any[]) {
12    for (const key of required) {
13      if (!this[key]) {
14        throw new Error(`${key} is required`);
15      }
16    }
17    return method.apply(this, args);
18  };
19}
20
21class User {
22  @required
23  name: string;
24
25  @required
26  email: string;
27
28  @validate
29  save() {
30    console.log('Saving user...');
31  }
32}

参数装饰器应用于方法的参数。

 1function required(target: any, propertyKey: string | symbol | undefined, parameterIndex: number) {
 2  const existingRequired = Reflect.getMetadata('required', target, propertyKey) || [];
 3  existingRequired.push(parameterIndex);
 4  Reflect.defineMetadata('required', existingRequired, target, propertyKey);
 5}
 6
 7class UserService {
 8  getUser(@required id: string, name?: string) {
 9    // id 参数被标记为必需
10  }
11}
 1function inject(token: string) {
 2  return function(target: any, propertyKey: string | symbol | undefined, parameterIndex: number) {
 3    const existingInjections = Reflect.getMetadata('injections', target, propertyKey) || [];
 4    existingInjections[parameterIndex] = token;
 5    Reflect.defineMetadata('injections', existingInjections, target, propertyKey);
 6  };
 7}
 8
 9class UserController {
10  constructor(
11    @inject('UserService') private userService: UserService,
12    @inject('Logger') private logger: Logger
13  ) {}
14}

访问器装饰器应用于 getter 和 setter。

 1function configurable(value: boolean) {
 2  return function(
 3    target: any,
 4    propertyKey: string,
 5    descriptor: PropertyDescriptor
 6  ) {
 7    descriptor.configurable = value;
 8  };
 9}
10
11class Point {
12  private _x: number;
13  private _y: number;
14
15  constructor(x: number, y: number) {
16    this._x = x;
17    this._y = y;
18  }
19
20  @configurable(false)
21  get x() {
22    return this._x;
23  }
24
25  @configurable(false)
26  get y() {
27    return this._y;
28  }
29}

使用 reflect-metadata 库来访问和操作元数据。

1npm install reflect-metadata
 1import 'reflect-metadata';
 2
 3// 定义元数据
 4Reflect.defineMetadata('key', 'value', target);
 5
 6// 获取元数据
 7const value = Reflect.getMetadata('key', target);
 8
 9// 检查元数据
10const hasMetadata = Reflect.hasMetadata('key', target);
11
12// 删除元数据
13Reflect.deleteMetadata('key', target);
 1import 'reflect-metadata';
 2
 3const ROUTE_METADATA_KEY = Symbol('route');
 4
 5function Route(path: string, method: string = 'get') {
 6  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
 7    Reflect.defineMetadata(ROUTE_METADATA_KEY, { path, method }, target, propertyKey);
 8  };
 9}
10
11class UserController {
12  @Route('/users', 'get')
13  getUsers() {
14    return ['John', 'Jane'];
15  }
16
17  @Route('/users/:id', 'get')
18  getUser(id: string) {
19    return { id, name: 'John' };
20  }
21}
22
23// 读取路由元数据
24function getRoutes(controller: any) {
25  const routes: any[] = [];
26  const prototype = Object.getPrototypeOf(controller);
27  
28  for (const propertyName of Object.getOwnPropertyNames(prototype)) {
29    const metadata = Reflect.getMetadata(ROUTE_METADATA_KEY, prototype, propertyName);
30    if (metadata) {
31      routes.push({
32        path: metadata.path,
33        method: metadata.method,
34        handler: prototype[propertyName]
35      });
36    }
37  }
38  
39  return routes;
40}

装饰器的执行顺序:

  1. 参数装饰器
  2. 方法/属性/访问器装饰器
  3. 类装饰器
 1function f(key: string) {
 2  console.log(`Evaluation: ${key}`);
 3  return function() {
 4    console.log(`Execution: ${key}`);
 5  };
 6}
 7
 8@f('Class')
 9class C {
10  @f('Static Property')
11  static prop?: number;
12
13  @f('Static Method')
14  static method(@f('Static Method Parameter') o: any) {}
15
16  @f('Instance Property')
17  prop?: number;
18
19  @f('Instance Method')
20  method(@f('Instance Method Parameter') o: any) {}
21}
 1import 'reflect-metadata';
 2
 3const INJECT_KEY = Symbol('inject');
 4
 5function Injectable() {
 6  return function<T extends { new (...args: any[]): {} }>(constructor: T) {
 7    return constructor;
 8  };
 9}
10
11function Inject(token: string) {
12  return function(target: any, propertyKey: string | symbol | undefined, parameterIndex: number) {
13    const existingInjections = Reflect.getMetadata(INJECT_KEY, target) || [];
14    existingInjections[parameterIndex] = token;
15    Reflect.defineMetadata(INJECT_KEY, existingInjections, target);
16  };
17}
18
19@Injectable()
20class UserService {
21  getUsers() {
22    return ['John', 'Jane'];
23  }
24}
25
26@Injectable()
27class UserController {
28  constructor(
29    @Inject('UserService') private userService: UserService
30  ) {}
31}
 1import 'reflect-metadata';
 2
 3function MinLength(length: number) {
 4  return function(target: any, propertyKey: string) {
 5    const existingValidators = Reflect.getMetadata('validators', target) || {};
 6    existingValidators[propertyKey] = existingValidators[propertyKey] || [];
 7    existingValidators[propertyKey].push({
 8      type: 'minLength',
 9      value: length
10    });
11    Reflect.defineMetadata('validators', existingValidators, target);
12  };
13}
14
15function Validate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
16  const method = descriptor.value;
17  const validators = Reflect.getMetadata('validators', target) || {};
18
19  descriptor.value = function(...args: any[]) {
20    for (const [key, rules] of Object.entries(validators)) {
21      for (const rule of rules as any[]) {
22        if (rule.type === 'minLength' && this[key].length < rule.value) {
23          throw new Error(`${key} must be at least ${rule.value} characters`);
24        }
25      }
26    }
27    return method.apply(this, args);
28  };
29}
30
31class User {
32  @MinLength(3)
33  name: string;
34
35  @Validate
36  save() {
37    console.log('Saving user...');
38  }
39}

TypeScript 的装饰器系统提供了强大的元编程能力:

  • 类装饰器:修改或替换类定义
  • 方法装饰器:修改方法行为(日志、性能监控等)
  • 属性装饰器:添加属性元数据
  • 参数装饰器:标记参数(依赖注入、验证等)
  • 访问器装饰器:修改 getter/setter
  • 元数据反射 API:存储和读取元数据

装饰器的应用场景:

  • 依赖注入:标记需要注入的依赖
  • 路由系统:定义 API 路由
  • 验证系统:添加验证规则
  • 日志和监控:自动添加日志和性能监控
  • ORM:定义数据库模型

在实际开发中:

  • 装饰器是实验性特性,需要显式启用
  • 使用 reflect-metadata 库来操作元数据
  • 理解装饰器的执行顺序
  • 装饰器主要用于框架开发,普通业务代码谨慎使用