TypeScript 装饰器高级用法详解
TypeScript 中的装饰器提供了强大的元编程能力,可以用于实现各种高级模式。下面我将深入介绍装饰器的高级用法。
1. 装饰器组合与执行顺序
1.1. 多装饰器组合
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
1.2. 执行顺序规则
1. 装饰器工厂从上到下执行;
2. 装饰器函数从下到上执行;
3. 不同类型的装饰器按特定顺序执行;
2. 元数据反射高级用法
结合 reflect-metadata 可以实现更强大的功能:
import "reflect-metadata";const requiredMetadataKey = Symbol("required");function required(target: Object, propertyKey: string | symbol, parameterIndex: number
) {const existingRequiredParameters: number[]= Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];existingRequiredParameters.push(parameterIndex);Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}function validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<Function>
) {const method = descriptor.value!;descriptor.value = function () {const requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName) || [];requiredParameters.forEach(parameterIndex => {if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined) {throw new Error(`Missing required argument at position ${parameterIndex}`);}});return method.apply(this, arguments);};
}class Greeter {@validategreet(@required name: string, title?: string) {return `Hello ${title || ''}${name}`;}
}const g = new Greeter();
g.greet("Alice"); // OK
g.greet(); // Error: Missing required argument at position 0
3. 方法拦截与AOP实现
3.1. 方法拦截器
function intercept(before: Function, after?: Function) {return function (target: any,propertyKey: string,descriptor: PropertyDescriptor) {const originalMethod = descriptor.value;descriptor.value = function (...args: any[]) {before.apply(this, args);const result = originalMethod.apply(this, args);if (after) after.apply(this, [result, ...args]);return result;};return descriptor;};
}class Calculator {@intercept((x: number, y: number) => console.log(`About to add ${x} and ${y}`),(result: number) => console.log(`Result is ${result}`))add(x: number, y: number): number {return x + y;}
}const calc = new Calculator();
calc.add(2, 3);
// About to add 2 and 3
// Result is 5
3.2. 异步方法拦截
function asyncIntercept() {return function (target: any,propertyKey: string,descriptor: PropertyDescriptor) {const originalMethod = descriptor.value;descriptor.value = async function (...args: any[]) {console.log(`Starting ${propertyKey} with args:`, args);try {const result = await originalMethod.apply(this, args);console.log(`Completed ${propertyKey} with result:`, result);return result;} catch (error) {console.error(`Error in ${propertyKey}:`, error);throw error;}};return descriptor;};
}class ApiService {@asyncIntercept()async fetchData(url: string) {const response = await fetch(url);return response.json();}
}
4. 依赖注入系统实现
4.1. 简易DI容器
import "reflect-metadata";const INJECTABLE_METADATA_KEY = Symbol("INJECTABLE_KEY");
const INJECT_METADATA_KEY = Symbol("INJECT_KEY");interface Type<T> {new(...args: any[]): T;
}class Container {private providers = new Map<symbol, any>();addProvider<T>(provider: T, identifier?: symbol) {const id = identifier || Symbol();this.providers.set(id, provider);return id;}inject(token: symbol) {return (target: any, key: string, index?: number) => {const metadata = Reflect.getMetadata(INJECT_METADATA_KEY, target) || {};metadata[key] = token;Reflect.defineMetadata(INJECT_METADATA_KEY, metadata, target);};}resolve<T>(target: Type<T>): T {const tokens = Reflect.getMetadata(INJECT_METADATA_KEY, target.prototype) || {};const injections = Object.keys(tokens).map(key => ({key,provider: this.providers.get(tokens[key])}));return new target(...injections.map(i => i.provider));}
}function Injectable() {return (target: any) => {Reflect.defineMetadata(INJECTABLE_METADATA_KEY, true, target);return target;};
}// 使用示例
const container = new Container();@Injectable()
class Logger {log(message: string) {console.log(`LOG: ${message}`);}
}const loggerToken = container.addProvider(new Logger());@Injectable()
class App {constructor(@container.inject(loggerToken) private logger: Logger) {}run() {this.logger.log("Application started");}
}const app = container.resolve(App);
app.run(); // 输出: LOG: Application started
5. 自定义装饰器工厂
5.1. 带参数的属性装饰器
function format(formatString: string) {return function (target: any, propertyKey: string) {let value = target[propertyKey];const getter = () => {return `${formatString} ${value}`;};const setter = (newVal: string) => {value = newVal;};Object.defineProperty(target, propertyKey, {get: getter,set: setter,enumerable: true,configurable: true,});};
}class Greeter {@format("Hello")greeting: string;constructor(message: string) {this.greeting = message;}
}const greeter = new Greeter("World");
console.log(greeter.greeting); // 输出: Hello World
5.2. 条件方法装饰器
function when(condition: boolean, decorator: MethodDecorator) {return function (target: any,propertyKey: string,descriptor: PropertyDescriptor) {if (condition) {return decorator(target, propertyKey, descriptor);}return descriptor;};
}class FeatureToggle {@when(process.env.NODE_ENV === 'development', log)experimentalMethod() {console.log("Running experimental feature");}
}
6. 装饰器与泛型结合
function logReturnType<T>() {return function (target: Object,propertyKey: string,descriptor: TypedPropertyDescriptor<(...args: any[]) => T>) {const originalMethod = descriptor.value!;descriptor.value = function (...args: any[]) {const result = originalMethod.apply(this, args);console.log(`Method ${propertyKey} returns type: ${typeof result}`);return result;};return descriptor;};
}class GenericExample {@logReturnType<number>()add(a: number, b: number): number {return a + b;}@logReturnType<string>()concat(a: string, b: string): string {return a + b;}
}const example = new GenericExample();
example.add(1, 2); // 输出: Method add returns type: number
example.concat("a", "b"); // 输出: Method concat returns type: string
7. 装饰器组合模式
function composeDecorators(...decorators: MethodDecorator[]) {return function (target: any,propertyKey: string,descriptor: PropertyDescriptor) {return decorators.reduce((desc, decorator) => {return decorator(target, propertyKey, desc) || desc;}, descriptor);};
}function logCall() {return function (target: any,propertyKey: string,descriptor: PropertyDescriptor) {const original = descriptor.value;descriptor.value = function (...args: any[]) {console.log(`Calling ${propertyKey} with`, args);return original.apply(this, args);};return descriptor;};
}function measureTime() {return function (target: any,propertyKey: string,descriptor: PropertyDescriptor) {const original = descriptor.value;descriptor.value = function (...args: any[]) {const start = performance.now();const result = original.apply(this, args);const end = performance.now();console.log(`Execution time: ${end - start}ms`);return result;};return descriptor;};
}class PerformanceExample {@composeDecorators(logCall(),measureTime())heavyCalculation(n: number) {let result = 0;for (let i = 0; i < n; i++) {result += Math.sqrt(i);}return result;}
}const perf = new PerformanceExample();
perf.heavyCalculation(1000000);
// 输出:
// Calling heavyCalculation with [1000000]
// Execution time: 12.345ms
8. 类装饰器扩展
8.1. 混入(Mixin)模式
type Constructor<T = {}> = new (...args: any[]) => T;function Timestamped<TBase extends Constructor>(Base: TBase) {return class extends Base {timestamp = Date.now();};
}function Loggable<TBase extends Constructor>(Base: TBase) {return class extends Base {log() {console.log(`Current timestamp: ${this.timestamp}`);}};
}class User {name: string;constructor(name: string) {this.name = name;}
}const TimestampedUser = Timestamped(User);
const LoggableTimestampedUser = Loggable(Timestamped(User));const user1 = new TimestampedUser("Alice");
console.log(user1.timestamp); // 输出当前时间戳const user2 = new LoggableTimestampedUser("Bob");
user2.log(); // 输出当前时间戳
9. 装饰器与React组件
// 高阶组件装饰器
function withLoadingIndicator(Component: React.ComponentType) {return function WithLoadingIndicator(props: any) {const [loading, setLoading] = React.useState(true);React.useEffect(() => {const timer = setTimeout(() => setLoading(false), 1000);return () => clearTimeout(timer);}, []);if (loading) {return <div>Loading...</div>;}return <Component {...props} />;};
}// 使用装饰器语法
@withLoadingIndicator
class MyComponent extends React.Component {render() {return <div>My Component Content</div>;}
}// 或者使用函数组件
const MyFunctionalComponent = withLoadingIndicator(() => (<div>My Functional Component</div>
));
10. 装饰器与性能优化
10.1. 记忆化(Memoization)装饰器
function memoize() {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 = JSON.stringify(args);if (cache.has(key)) {console.log(`Cache hit for ${propertyKey}(${key})`);return cache.get(key);}const result = originalMethod.apply(this, args);cache.set(key, result);console.log(`Cache miss for ${propertyKey}(${key})`);return result;};return descriptor;};
}class ExpensiveOperations {@memoize()fibonacci(n: number): number {if (n <= 1) return n;return this.fibonacci(n - 1) + this.fibonacci(n - 2);}
}const ops = new ExpensiveOperations();
console.log(ops.fibonacci(10)); // 计算并缓存结果
console.log(ops.fibonacci(10)); // 从缓存读取