如何基于泛型参数值获取TypeScript函数的参数类型?
泛型函数参数类型的动态推导问题
在TypeScript中,我们可以让函数参数的类型依赖于另一个泛型参数,比如:
// foo(true, (v /* 类型为1 */) => void 0) // foo(false, (v /* 类型为2 */) => void 0) const foo = <T extends boolean>(a: T, cb: (v: T extends true ? 1 : 2) => void) => {}
另外,我们可以通过typeof获取函数类型,再用Parameters<typeof foo>提取它的参数类型:
const foo = (a: number, b: string) => {} // baz的回调参数类型和foo的参数类型完全一致 const baz = (cb: (...args: Parameters<typeof foo>) => void) => {}
但如果面对的是泛型函数且原函数定义未知的情况,该怎么实现类似的类型推导?比如下面的场景:
// 这个函数可能是未知的,或者存在重载 const foo = <T extends boolean>(args: T, cb: (v: T extends true ? 1 : 2) => void) => {} const bar = <T extends Parameters<typeof foo>[0]>( args: T, // 这里的写法不生效,只是示例 // 期望能根据第一个参数T,获取foo对应情况下的回调参数类型 cb: Parameters<(typeof foo)[T]>[0] ) => {} bar(true, (v /* 期望类型为1 */) => void 0)
核心需求是:如何根据第一个参数的泛型值T,动态获取泛型函数foo对应的回调参数类型?
解决方案:用条件类型提取对应泛型下的参数类型
我们可以定义一个工具类型,专门用来提取泛型函数在指定第一个参数类型T时,第二个参数的类型。具体实现如下:
// 工具类型:获取泛型函数F在第一个参数为T时的第二个参数类型 type GetSecondParam<F, T> = F extends <U extends boolean>(args: U, cb: infer C) => void ? (U extends T ? C : never) : never; // 原泛型函数 const foo = <T extends boolean>(args: T, cb: (v: T extends true ? 1 : 2) => void) => {} // 实现bar函数 const bar = <T extends boolean>( args: T, cb: GetSecondParam<typeof foo, T> ) => {} // 测试:v的类型会正确推导为1 bar(true, (v) => { console.log(v); // v: 1 }); // 测试:v的类型会正确推导为2 bar(false, (v) => { console.log(v); // v: 2 });
原理说明
- 工具类型
GetSecondParam:- 先判断
F是否符合<U extends boolean>(args: U, cb: infer C) => void的结构,通过infer C提取出回调参数的类型C - 再通过条件类型
U extends T ? C : never,筛选出当第一个参数类型匹配T时对应的回调类型C
- 先判断
bar函数的泛型约束:直接将T约束为boolean(和foo的泛型约束保持一致),然后用GetSecondParam<typeof foo, T>获取对应类型的回调参数
如果原函数的泛型约束不是boolean,只需要调整工具类型里的泛型约束即可,比如如果是string | number,就把U extends boolean改成U extends string | number。
处理函数重载的情况
如果foo存在重载,我们可以调整工具类型,遍历所有重载签名,找到匹配的那个:
// 支持重载的工具类型 type GetSecondParamOverloaded<F, T> = F extends { (...args: infer A1): void; (...args: infer A2): void; (...args: infer A3): void; } ? (A1[0] extends T ? A1[1] : A2[0] extends T ? A2[1] : A3[0] extends T ? A3[1] : never) : never; // 带重载的foo函数 function foo(args: true, cb: (v: 1) => void): void; function foo(args: false, cb: (v: 2) => void): void; function foo<T extends boolean>(args: T, cb: (v: T extends true ? 1 : 2) => void) {} const bar = <T extends boolean>( args: T, cb: GetSecondParamOverloaded<typeof foo, T> ) => {} bar(true, (v) => { v; // 类型为1 }) bar(false, (v) => { v; // 类型为2 })
这种方式可以覆盖大多数重载场景,如果重载更多,可以继续扩展工具类型里的A4、A5等。
内容的提问来源于stack exchange,提问作者Simon
相关产品推荐
相关产品推荐

