====== 第八章 装饰器 ====== 装饰器(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 = ( target: TFunction ) => TFunction | void; 如果类装饰器返回一个值,它会使用返回的构造函数替换类声明。 // 返回新构造函数的类装饰器 function classDecorator( 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 = ( target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor ) => TypedPropertyDescriptor | 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 ) { 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(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开发至关重要。