TypeScript装饰器类型推断:链式API如何反向传递属性类型
链式TypeScript装饰器的反向类型推断解决方案
在构建链式API的TypeScript装饰器库时,会遇到类型推断失效的问题:当使用链式调用(如@Log.SubMember(m => m.hour).To("file"))时,闭包参数m的类型无法从被装饰字段反向推断,只能得到unknown;而单次调用的装饰器(如@LogSubMember(m => m.blah))能正常工作,因为TypeScript可通过ClassFieldDecoratorContext直接推断类型。
核心问题在于:TypeScript的类型推断默认是正向的,而装饰器需要反向推断(从被装饰目标到装饰器参数),链式调用会打断这个反向推断链,导致中间方法无法获取后续装饰器应用时的类型信息。
具体实现方案
1. 定义链式装饰器类型
将链式装饰器设计为包含装饰器函数签名的泛型类型,让所有链式方法共享同一组泛型参数,确保类型能在推断后完整传递:
// 核心链式装饰器类型,集成装饰器函数和链式方法 type LogDecorator<C, M> = { // 最终装饰器函数,触发反向类型推断的入口 (target: undefined, context: ClassFieldDecoratorContext<C, M>): void; // 链式方法:添加成员访问逻辑,返回同类型的装饰器 SubMember(accessor: (member: M, cls: C) => void): LogDecorator<C, M>; // 链式方法:添加输出目标配置,返回同类型的装饰器 To(target: string): LogDecorator<C, M>; }; // 顶层入口工厂,不预先绑定泛型参数,留待装饰器应用时推断 type LogFactory = { SubMember<C, M>(accessor: (member: M, cls: C) => void): LogDecorator<C, M>; };
2. 实现链式API(用Proxy保存配置)
用Proxy封装链式调用的状态(如accessor函数、输出目标),确保配置能在链式调用中累积,最终在装饰器执行时触发逻辑:
const Log: LogFactory = new Proxy({}, { get(target, prop) { if (prop === 'SubMember') { return (accessor: (member: any, cls: any) => void) => { // 保存链式调用中的配置信息 const config = { accessors: [accessor], outputTarget: 'default' }; // 返回链式装饰器实例 return new Proxy({}, { get(target, innerProp) { // 处理To方法:更新输出目标 if (innerProp === 'To') { return (targetStr: string) => { config.outputTarget = targetStr; return this.get(target, innerProp); }; } // 处理SubMember方法:追加成员访问逻辑 else if (innerProp === 'SubMember') { return (newAccessor: (member: any, cls: any) => void) => { config.accessors.push(newAccessor); return this.get(target, innerProp); }; } // 处理装饰器函数调用:执行所有累积的配置逻辑 else if (typeof innerProp === 'symbol' || innerProp === 'valueOf') { return (target: undefined, context: ClassFieldDecoratorContext<any, any>) => { // 实际业务逻辑:执行所有accessor,输出到目标位置 config.accessors.forEach(fn => fn(context.get(), context.this)); console.log(`[Log] ${context.name} -> ${config.outputTarget}`); }; } return undefined; } }) as LogDecorator<any, any>; }; } return undefined; } }) as LogFactory;
3. 测试使用
此时链式调用的类型推断完全正常:
class Example { // 单次调用:类型推断正常 @Log.SubMember(m => m.blah) private foo: { blah: number; blurb: number }; // 链式调用:m的类型正确推断为{ hour: number; minute: number } @Log.SubMember(m => m.hour).To("file") private bar: { hour: number; minute: number }; // 多次链式调用:类型推断依然有效 @Log.SubMember(m => m.hour) .SubMember(m => m.minute) .To("console") private baz: { hour: number; minute: number }; }
方案原理
- 反向推断触发:最终的
LogDecorator<C, M>包含ClassFieldDecoratorContext<C, M>签名,当装饰器应用到字段时,TypeScript会自动从字段类型和类类型反向推断出C(类类型)和M(成员类型)。 - 类型传递:所有链式方法返回同一泛型类型
LogDecorator<C, M>,推断出的C和M会自动传递给所有链式方法的参数,让闭包中的member和cls获得正确类型。 - 状态累积:通过Proxy或闭包保存链式调用中的配置,在最终装饰器执行时统一处理所有逻辑。
注意事项
- 链式方法必须返回同一个泛型类型
LogDecorator<C, M>,不能中途切换类型,否则会打断类型传递链。 - 若需扩展更多链式操作,只需在
LogDecorator中添加对应方法并返回LogDecorator<C, M>即可。 - 实际实现中可替换Proxy为闭包模式,降低复杂度。
内容的提问来源于stack exchange,提问作者TradeItEasy
相关产品推荐
相关产品推荐

