当参数为函数时TypeScript泛型参数无法自动推断的原因与解决办法
TypeScript泛型推断问题:元组与函数类型的差异
场景复现
情况1:GETR为元组类型时无错误
当GETR被定义为元组类型[a,b]时,调用foo({ bar })可以正常推断泛型参数,无类型错误:
type GETR<a extends string, b extends string> = [a,b] interface Options<A extends string, B extends string> { bar: GETR<A,B> } function foo<A extends string, B extends string, C extends Options<A, B>>(r: C) { return r } const bar:GETR<"foo", "bar"> = null as any as GETR<"foo", "bar"> const x = foo({ bar })
情况2:GETR改为函数类型后出现错误
仅将GETR改为函数类型(k:a) => b,调用foo({ bar })时泛型参数无法自动推断,出现类型错误:
type GETR<a extends string, b extends string> = (k:a) => b interface Options<A extends string, B extends string> { bar: GETR<A,B> } function foo<A extends string, B extends string, C extends Options<A, B>>(r: C) { return r } const bar:GETR<"foo", "bar"> = null as any as GETR<"foo", "bar"> const x = foo({ bar })
问题原因
TypeScript对元组类型和函数类型的泛型推断逻辑存在差异:
- 元组类型是结构化的具体类型,它的元素类型会被TypeScript直接作为泛型参数的推断依据,能从
bar的GETR<"foo", "bar">类型中直接提取出"foo"和"bar"来匹配A和B。 - 函数类型属于带参数签名的抽象类型,TypeScript不会自动从一个已标注好具体类型的函数变量中反向拆解出它的参数类型和返回值类型。只有当传入的是未预先标注类型的函数实现时,TypeScript才会根据实现代码推断参数和返回值类型;而这里的
bar是已经被固定为GETR<"foo", "bar">的变量,TypeScript不会主动拆解这个类型去推导A和B。
最佳修复方案
方案1:简化泛型约束(推荐)
去掉多余的C泛型,让foo直接接收Options<A,B>类型的参数,TypeScript会直接从bar的类型中匹配推断A和B:
type GETR<a extends string, b extends string> = (k:a) => b interface Options<A extends string, B extends string> { bar: GETR<A,B> } function foo<A extends string, B extends string>(r: Options<A, B>) { return r } const bar:GETR<"foo", "bar"> = null as any as GETR<"foo", "bar"> const x = foo({ bar }) // 正常推断,无错误
方案2:使用infer反向提取类型参数
如果必须保留C泛型,可以通过infer关键字从C["bar"]中反向提取A和B的类型:
type GETR<a extends string, b extends string> = (k:a) => b interface Options<A extends string, B extends string> { bar: GETR<A,B> } function foo<C extends Options<any, any>>(r: C) { type ExtractedA = C["bar"] extends GETR<infer T, any> ? T : never type ExtractedB = C["bar"] extends GETR<any, infer U> ? U : never return r as Options<ExtractedA, ExtractedB> } const bar:GETR<"foo", "bar"> = null as any as GETR<"foo", "bar"> const x = foo({ bar }) // 正常推断
内容的提问来源于stack exchange,提问作者JD Isaacks
相关产品推荐
相关产品推荐

