You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 };
}

方案原理

  1. 反向推断触发:最终的LogDecorator<C, M>包含ClassFieldDecoratorContext<C, M>签名,当装饰器应用到字段时,TypeScript会自动从字段类型和类类型反向推断出C(类类型)和M(成员类型)。
  2. 类型传递:所有链式方法返回同一泛型类型LogDecorator<C, M>,推断出的C和M会自动传递给所有链式方法的参数,让闭包中的member和cls获得正确类型。
  3. 状态累积:通过Proxy或闭包保存链式调用中的配置,在最终装饰器执行时统一处理所有逻辑。

注意事项

  • 链式方法必须返回同一个泛型类型LogDecorator<C, M>,不能中途切换类型,否则会打断类型传递链。
  • 若需扩展更多链式操作,只需在LogDecorator中添加对应方法并返回LogDecorator<C, M>即可。
  • 实际实现中可替换Proxy为闭包模式,降低复杂度。

内容的提问来源于stack exchange,提问作者TradeItEasy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 06:07:02