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

如何基于递归对象生成带点路径的TypeScript判别联合类型?

动态生成嵌套回调的判别联合类型

我在TypeScript里定义了一个嵌套的回调函数对象:

type CallbackFn = (args: any) => any
type CallbackObj = {
  [key: string]: CallbackFn | CallbackObj
}
const callbacks = {
  foo: function(args: { x: number }): string {
    return "test";
  },
  bar: {
    bar1: function(): boolean {
      return true;
    },
    bar2: function(): number {
      return 10;
    }
  },
  baz: {
    baz1: {
      baz2: function(args: { key: string }): string {
        return "test";
      }
    }
  }
}

还有这些接口定义:

interface FieldDef {
  name: string
  type: string
  callback: CallbackDef
}

interface CallbackDef {
  fn: string
  settings: any
}

我的目标是:当用户声明FieldDef的回调时,能自动补全可选的回调路径(比如"foo"|"bar.bar1"|"bar.bar2"|"baz.baz1.baz2"),同时自动补全对应回调的settings参数类型。理想的CallbackDef应该是这样的判别联合类型:

type CallbackDef = {
    name: "foo",
    settings: {
        x: number
    }
} | {
    name: "bar.bar1"
} | {
    name: "bar.bar2"
} | {
    name: "baz.baz1.baz2",
    settings: {
        key: string
    }
}

但我没法基于已有的callbacks对象动态生成这个联合类型,现在卡在两个点:

  • 递归处理多层嵌套的类型
  • 常规索引类型没法处理对象节点下多个函数的情况

我目前的实现代码是:

type CallbackFn = (args: any) => any
type CallbackObj = {
    [key: string]: CallbackFn | CallbackObj
}
const callbacks = {
    foo: function(args: { x: number }): string {
        return "test";
    },
    bar: {
        bar1: function(): boolean {
            return true;
        },
        bar2: function(): number {
            return 10;
        }
    },
    baz: {
        baz1: {
            baz2: function(args: { key: string }): string {
            return "test";
            }
        }
    }
}

type StringKeys<T> = Extract<keyof T, string>;

type Process<T> = {
    [key in StringKeys<T>]: T[key] extends CallbackFn
    ? { [k in key]: T[key] }
    : {
        [k in StringKeys<T[key]> as `${key}.${k}`]: T[key][k]
    }
}

type GetValues<T> = T[keyof T];

type A = Process<typeof callbacks>
type B = GetValues<A>

求更简洁的实现方式。


解决方案

可以通过递归类型遍历嵌套结构,自动提取所有回调路径和对应的参数类型:

type CallbackFn = (args: any) => any
type CallbackObj = {
  [key: string]: CallbackFn | CallbackObj
}

const callbacks = {
  foo: function(args: { x: number }): string {
    return "test";
  },
  bar: {
    bar1: function(): boolean {
      return true;
    },
    bar2: function(): number {
      return 10;
    }
  },
  baz: {
    baz1: {
      baz2: function(args: { key: string }): string {
        return "test";
      }
    }
  }
}

// 提取对象的字符串键
type StringKeys<T> = Extract<keyof T, string>;

// 递归提取所有回调路径
type ExtractCallbackPaths<T, Prefix extends string = ""> = {
  [K in StringKeys<T>]: T[K] extends CallbackFn
    ? Prefix extends "" ? K : `${Prefix}.${K}`
    : ExtractCallbackPaths<T[K], Prefix extends "" ? K : `${Prefix}.${K}`>
}[StringKeys<T>];

// 根据路径解析对应的回调函数类型
type ParsePath<T, Path extends string> = 
  Path extends `${infer K}.${infer Rest}`
    ? K extends StringKeys<T>
      ? T[K] extends CallbackObj
        ? ParsePath<T[K], Rest>
        : never
      : never
    : Path extends StringKeys<T>
      ? T[Path] extends CallbackFn
        ? T[Path]
        : never
      : never;

// 获取回调函数的参数类型,无参数则返回never
type CallbackArgs<T extends CallbackFn> = 
  T extends (args: infer A) => any ? A : never;

// 生成最终的CallbackDef联合类型
type CallbackDef = {
  [Path in ExtractCallbackPaths<typeof callbacks>]: {
    name: Path;
    settings: CallbackArgs<ParsePath<typeof callbacks, Path>> extends never
      ? undefined
      : CallbackArgs<ParsePath<typeof callbacks, Path>>;
  }
}[ExtractCallbackPaths<typeof callbacks>];

// 测试:自动补全和类型提示
const field: FieldDef = {
  name: "test",
  type: "string",
  callback: {
    name: "baz.baz1.baz2",
    settings: { key: "hello" } // 自动提示key:string类型
  }
};

关键说明

  • ExtractCallbackPaths:递归遍历嵌套对象,拼接所有回调函数的完整路径,生成路径联合类型
  • ParsePath:根据路径字符串反向查找对应的回调函数类型
  • CallbackArgs:提取回调函数的参数类型,无参数时返回never,最终转为undefined(可根据需求调整为可选属性)
  • 最终生成的CallbackDef是判别联合类型,选中不同name时,settings会自动匹配对应参数类型,实现自动补全

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 13:05:42