TypeScript 5中如何以编程方式应用装饰器?
TypeScript 5 中编程式应用装饰器的可行方案
在TypeScript 5中完全可以编程式应用装饰器,尽管新版装饰器引入了包含详细元信息的DecoratorContext参数增加了复杂度,但只要遵循ES标准装饰器的调用规范,就能实现批量应用装饰器的需求。
核心思路
TypeScript 5采用了符合ES标准的装饰器实现,装饰器函数现在接收三个核心参数:
- 装饰目标(比如类原型、方法函数、属性初始值)
- 装饰上下文(
DecoratorContext,包含kind、name、access等元信息) - 额外参数(部分装饰器类型会用到,比如参数装饰器的参数索引)
批量应用装饰器的本质是:按正确顺序调用每个装饰器,传递对应的参数,并处理装饰器的返回值(如果装饰器返回了新的目标,需要用它作为下一个装饰器的输入)。
实现批量装饰器的高阶函数
可以封装一个applyDecorators函数,接收多个装饰器并返回一个组合后的装饰器,自动处理调用顺序和参数传递:
示例代码
首先定义两个测试用的装饰器:
// 统计方法执行时间的装饰器 function time(target: any, context: ClassMethodDecoratorContext) { const methodName = context.name as string; return function(this: any, ...args: any[]) { console.time(methodName); const result = target.apply(this, args); console.timeEnd(methodName); return result; }; } // 打印方法入参的装饰器 function logArgs(target: any, context: ClassMethodDecoratorContext) { return function(this: any, ...args: any[]) { console.log(`调用方法 ${String(context.name)},参数:`, args); return target.apply(this, args); }; }
然后实现applyDecorators组合函数:
type Decorator = (target: any, context: DecoratorContext, ...rest: any[]) => any; function applyDecorators<T extends Decorator>(...decorators: T[]): T { return function(target: any, context: DecoratorContext, ...rest: any[]) { let currentTarget = target; // ES装饰器按从右到左顺序应用,因此需要倒序遍历装饰器数组 for (const decorator of decorators.reverse()) { const decoratedResult = decorator(currentTarget, context, ...rest); // 如果装饰器返回了新的目标,更新为当前处理对象 if (decoratedResult !== undefined) { currentTarget = decoratedResult; } } return currentTarget; } as T; }
使用方式
直接在类成员上使用组合后的装饰器:
class MyClass { @applyDecorators(time, logArgs) method(a: number): void { console.log("MyClass.method()"); } } // 调用测试 new MyClass().method(123); // 输出顺序: // 调用方法 method,参数: [123] // MyClass.method() // method: 0.xxms
手动编程式应用装饰器
如果需要更灵活的场景(比如在类定义后动态添加装饰器),可以手动构造DecoratorContext并调用装饰器:
class MyClass { method(a: number): void { console.log("MyClass.method()"); } } // 构造方法装饰的上下文对象 const methodContext: ClassMethodDecoratorContext = { kind: "method", name: "method", static: false, private: false, access: { has: (obj) => "method" in obj, get: (obj) => obj.method }, addInitializer: (init) => { // 若需要添加实例初始化逻辑,在此处处理(比如加入类的初始化队列) } }; // 手动调用装饰器并替换原方法 const decoratedMethod = time(MyClass.prototype.method, methodContext); if (decoratedMethod) { MyClass.prototype.method = decoratedMethod; }
关键注意事项
- 装饰器顺序:ES标准装饰器的应用顺序是从右到左(即代码中从上到下的反向),
@applyDecorators(A, B)等价于@A @B,需通过倒序遍历保证正确执行顺序。 - 返回值处理:部分装饰器(比如方法装饰器)会返回新的函数替代原目标,必须将这个返回值传递给下一个装饰器,否则装饰器效果会丢失。
- 上下文完整性:手动构造
DecoratorContext时,要确保access、kind等核心属性正确,否则装饰器可能无法正常工作。
内容的提问来源于stack exchange,提问作者Ivan Mushketyk
相关产品推荐
相关产品推荐

