导读:本期聚焦于三上悠亚创作的《TypeScript条件类型中infer待推断类型位于多重嵌套场景时如何正确推断?》,敬请观看详情。infer关键字让TypeScript的条件类型具备了模式匹配能力,可从既有类型中提取出目标部分。但当待推断类型出现在多重嵌套的条件类型里,推断结果经常不符合预期,比如推断出联合类型变成了never,或者分布条件类型导致分支行为异常。本文从条件类型的匹配规则入手,分析infer在嵌套结构中的推断顺序、协变与逆变的合成规则,并通过提取函数返回值深层嵌套Promise、递归扁平化数组类型等实例,讲解如何写出正确的嵌套条件类型,同时指出常见误区与调试手段,帮助掌握复杂类型体操的编写思路。

TypeScript的条件类型配合infer关键字,可以实现类似模式匹配的类型提取能力。单个infer的用法大多数人都能掌握,但一旦待推断类型被放进多重嵌套的条件类型中,推断结果就变得难以预测。同样的写法,有时得到期望的精确类型,有时却得到never或者unknown。这篇文章围绕嵌套条件类型中infer的推断规则展开,分析协变与逆变位置对推断结果的影响,并给出几个典型的类型体操实例。

TypeScript条件类型中infer待推断类型位于多重嵌套场景时如何正确推断?

条件类型的匹配机制与infer的基本原理

要理解嵌套场景下的推断行为,首先要知道条件类型的匹配规则。条件类型的语法是T extends U ? X : Y,编译器会先检查T是否可以分配给U。当U中出现了infer R这样的占位符时,编译器会尝试把T的结构与U进行匹配,一旦匹配成功,就把T中对应位置的具体类型赋给R。这个过程本质上是一种结构化的模式匹配。

匹配的前提是extends两边的结构能够对齐。比如T extends Promise<infer R> ? R : T这个经典写法,当T确实是Promise结构时,R就被推断为Promise内部的包装类型;否则走false分支返回T本身。这里的关键在于:infer所在的位置必须和T中对应位置的结构严格对齐,多一层或少一层包装都会导致匹配失败。

还有一个容易被忽略的点:当传入的类型参数是联合类型时,条件类型默认是分布式的。也就是说(A | B) extends Promise<infer R>会被拆成A extends Promise<infer R>B extends Promise<infer R>分别判断,最后把各分支的结果重新组成联合类型。这个特性在嵌套条件类型中会逐层生效,是很多意外结果的来源。

多重嵌套时infer的推断顺序与协变逆变规则

当infer出现在函数参数、返回值等多个位置时,推断结果取决于该位置的型变性质。官方规则可以概括为:多个同位置的候选类型如果处于协变位置(比如返回值),最终合成联合类型;处于逆变位置(比如函数参数),最终合成交叉类型。如果候选类型同时出现在协变和逆变位置,则直接推断为never。理解这条规则,是写对嵌套infer的基础。

来看一个从对象属性中提取类型的例子。假设有一个嵌套的条件类型,想提取所有函数的返回值:

type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type Obj = {
  a: () => string;
  b: () => number;
};

// 借助映射类型逐个属性处理,infer在每一层条件类型中独立推断
type AllReturns<T> = {
  [K in keyof T]: T[K] extends (...args: any[]) => infer R ? R : never
}[keyof T];

type Result = AllReturns<Obj>; // string | number

这里infer虽然只写了一次,但通过映射类型被应用到了每个属性上,每个属性独立走一遍条件判断。由于返回值位置是协变的,多个候选string和number最终合成为联合类型string | number。如果把infer放到参数位置,结果就会变成交叉类型,比如string & number,而这个交叉类型实际上是never,因为没有任何类型能同时是string和number。

嵌套还带来一个顺序问题。多重条件类型是从外向内逐层求值的,外层的条件判断结果决定了内层拿到的类型。如果外层判断已经把类型缩小,内层的infer就在缩小后的类型上匹配。写嵌套时建议把每一层的职责想清楚:外层负责剥壳,内层负责提取,不要让两层infer抢同一个位置。

递归嵌套提取深层Promise的实战

异步代码经过async修饰后,返回值会被Promise层层包裹。想拿到最里层的类型,就需要递归条件类型配合infer。这是嵌套推断最经典的应用场景:

type Awaited<T> = T extends PromiseLike<infer V>
  ? Awaited<V>   // 匹配成功就继续剥下一层
  : T;           // 不再是Promise时返回自身

async function fetchUser() {
  return fetchUser();
}

type Deep = Awaited<ReturnType<typeof fetchUser>>; // 递归到底的具体类型

这段代码的关键在于递归调用自身时,V已经比T少了一层Promise包装。TypeScript从4.1版本开始支持递归条件类型,但要求递归必须最终能够终止,也就是某一层要能走到false分支。如果模式写得不对,比如模式本身每次匹配都不消耗包装层,就会报类型实例化过深的错误。

类似思路还可以用来递归扁平化数组。利用分布条件类型把联合类型的数组逐个拆开:

type Flatten<T> = T extends Array<infer Inner> ? Flatten<Inner> : T;

type Nested = Array<Array<Array<number>>>;
type Flat = Flatten<Nested>; // number

注意这里的技巧是模式写成Array<infer Inner>而不是infer Inner[],前者兼容元组和readonly数组,适用范围更广。当传入的是数组的联合类型时,分布行为会把每个成员分别递归处理,最后合并成扁平的联合类型,这正是我们想要的语义。

常见误区与调试手段

第一个常见误区是忘记阻止分布行为。如果希望对联合类型整体判断而不是逐个成员判断,需要用方括号包裹:[T] extends [U]。比如判断T是否恰好是boolean类型,直接写T extends boolean ? ...会把true和false拆开分别处理,用元组包裹才能拿到整体视角。

第二个误区是在同一层的多个位置使用同名infer变量,导致协变逆变冲突推断出never。解决办法是给不同位置的infer起不同的名字,再在外层分别引用,或者用条件类型逐个提取。

调试嵌套条件类型时,最实用的办法是借助IDE的类型悬停提示,从最内层开始逐层验证。也可以定义临时类型别名,把中间结果打印出来。另一个工具是官方发行说明中的可分配性检查技巧:用一个故意的赋值错误,让编译器在错误信息中展示它推断出的具体类型:

type Debug<T> = T extends Promise<infer R> ? R : never;

type Check = Debug<Promise<Promise<string>>>;
// 用赋值错误触发提示,观察编译器给出的类型
declare const check: Check;
const target: string = check; // 报错信息会展示真实推断结果

总的来说,多重嵌套条件类型中的infer推断遵循明确规则:外层到内层逐层求值、分布行为逐层传播、协变合成联合、逆变合成交叉、冲突则never。把这几条规则记牢,再配合递归模式处理深层结构,大部分复杂的类型体操问题都能迎刃而解。遇到不符合预期的结果时,先检查分布行为是否被触发,再检查infer是否同时出现在协变和逆变位置,通常就能定位到问题所在。

TypeScript条件类型infer类型推断修改时间:2026-09-04 11:04:49

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260904/50197.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。