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}
元数据反射 API
使用 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}
装饰器执行顺序
装饰器的执行顺序:
- 参数装饰器
- 方法/属性/访问器装饰器
- 类装饰器
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}
实际应用场景
Angular 风格的依赖注入
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库来操作元数据 - 理解装饰器的执行顺序
- 装饰器主要用于框架开发,普通业务代码谨慎使用