TypeScript条件类型:如何正确获取嵌套Iterable类型?
双层嵌套泛型类型推断的差异机制解析
想要定义一个类型,从Iterable<Iterable<A>>结构中提取出内部的A类型。但两种不同的类型定义方式得到了截然不同的结果:
const arr = [ [1, 2, 3], [4, 5, 6], ] type GoodNestedIterableType<A> = A extends Iterable<infer B> ? B extends Iterable<infer C> ? C : never : never type GoodExtractedType = GoodNestedIterableType<typeof arr> // number type BadNestedIterableType<A> = A extends Iterable<Iterable<infer B>> ? B : never type BadExtractedType = BadNestedIterableType<typeof arr> // unknown
GoodExtractedType能正确推断出number,但BadExtractedType却得到了unknown。而当直接传入Iterable<Iterable<number>>时,BadNestedIterableType又能正常提取number。
核心机制:条件类型的泛型推断顺序与匹配逻辑
这个差异源于TypeScript处理嵌套泛型约束推断和分步泛型推断的逻辑不同:
- 分步推断(GoodNestedIterableType)的工作方式
当使用两层嵌套的条件类型时:
- 第一步,TypeScript将
typeof arr(即number[][],等价于Array<Array<number>>)与Iterable<infer B>匹配,提取出B为Array<number>(因为Array<T>实现了Iterable<T>,其迭代元素类型就是数组的元素类型)。 - 第二步,再将
B(Array<number>)与Iterable<infer C>匹配,此时TypeScript识别到Array<number>同样实现了Iterable<number>,因此提取出C为number。
这种分步处理的方式,让TypeScript可以逐层解析嵌套结构,每一步都单独处理一个Iterable约束,自然能正确提取最内层的类型。
- 一次性嵌套推断(BadNestedIterableType)的局限
当直接使用A extends Iterable<Iterable<infer B>>时,TypeScript需要一次性将输入类型与整个嵌套的Iterable<Iterable<infer B>>结构匹配:
typeof arr是Array<Array<number>>,它实现的是Iterable<Array<number>>,而非直接的Iterable<Iterable<number>>。- TypeScript在这种一次性匹配场景下,不会自动将内层的
Array<number>向上转换为它所实现的Iterable<number>接口来完成推断。它只会检查Array<number>是否直接匹配Iterable<infer B>的结构,而由于Array是具体类型,Iterable是接口,这种直接匹配失败,最终只能将B推断为unknown。
- 直接传入
Iterable<Iterable<number>>时的例外情况
当输入类型就是精确的Iterable<Iterable<number>>时,TypeScript可以直接将其与Iterable<Iterable<infer B>>匹配,无需处理具体类型到接口的适配,因此能正确提取B为number。
扩展到非Iterable场景
这种逻辑不限于Iterable接口,所有涉及具体类型实现嵌套接口的场景都会出现类似现象。比如自定义容器接口:
interface Container<T> { content: T } class Box<T> implements Container<T> { constructor(public content: T) {} } const nestedBox = new Box(new Box(123)); // 分步推断:正确提取number type GoodExtract<T> = T extends Container<infer U> ? U extends Container<infer V> ? V : never : never; type GoodResult = GoodExtract<typeof nestedBox>; // number // 一次性嵌套推断:得到unknown type BadExtract<T> = T extends Container<Container<infer V>> ? V : never; type BadResult = BadExtract<typeof nestedBox>; // unknown
这里Box<Box<number>>实现了Container<Box<number>>,但一次性匹配Container<Container<infer V>>时,TypeScript不会自动将Box<number>转换为Container<number>来完成推断,因此只能得到unknown。
内容的提问来源于stack exchange,提问作者Sidd Singal
相关产品推荐
相关产品推荐

