先记住这个答案
条件类型中的 infer 可以从匹配结构提取类型,同一个推断变量出现多个候选时,结果取决于候选位置和结构关系。典型输出位置的多个候选可能合并为联合,函数参数这类输入位置的候选可能形成交叉,以满足相应兼容要求。对重载函数提取返回类型时,通常从最后一个声明签名推断,并不会逐个模拟实际参数调用来选择最佳重载。还要把这些规则与条件类型对联合输入的分发分开分析,不能统一概括成 infer 总取联合或总取最后一个类型。
- 先确定多个候选来自结构、分发还是重载
- 输出与输入位置的候选合并方向可能不同
- 重载类型提取不等于一次真实调用的重载选择
为什么输出候选可以合并为联合
一个对象的两个输出字段分别提供字符串和数字,用同一个 infer 变量描述它们时,联合可以容纳两种输出。若候选来自两个函数的输入位置,则需要考虑什么输入能同时满足相关处理能力,典型结果会表现为交叉约束。
示例分别提取字段值和函数输入,故意用两个不同对象结构作为输入候选,让交叉结果仍有可构造的意义。若直接使用互斥基础类型,交叉可能归约为 never,容易让读者只记住一个空结果而忽略背后的兼容关系。
type Values<T> = T extends { left: infer U; right: infer U } ? U : never;
type Inputs<T> = T extends {
first: (value: infer U) => void;
second: (value: infer U) => void;
} ? U : never;
type OutputChoice = Values<{ left: string; right: number }>;
type InputRequirement = Inputs<{
first: (value: { id: string }) => void;
second: (value: { name: string }) => void;
}>;
type CallResult<F> = F extends (...args: never[]) => infer R ? R : never;
declare function convert(value: string): number;
declare function convert(value: number): string;
declare function convert(value: string | number): string | number;
type OverloadResult = CallResult<typeof convert>;OutputChoice 为 string | number,InputRequirement 同时要求 id 与 name。OverloadResult 根据最后的联合签名得到 string | number;声明函数这里只提供类型,不构成可直接运行的函数实现。
重载最后签名与实际调用不是同一过程
真实调用 convert 时,编译器会根据传入参数匹配可用重载;提取整个函数类型的返回类型时,并没有某组实际参数参与选择,因此不能期待它分别得到所有输入与输出的一一对应表。最后的宽签名通常承担概括可调用形态的角色。
如果最后一个公开重载只是某个窄分支,而没有概括所有分支的签名,提取结果也可能只反映那个窄返回类型。实现签名是否对外可见同样需要分清。库工具若依赖重载顺序,应通过消费端类型测试固定预期,避免调整声明顺序后静默改变结果。
复杂推断需要保留可解释性
联合输入可能先分发,再在每个成员内合并候选,最后又形成新的联合。调试时可以把工具拆成中间类型别名,逐步检查每一层结果。只看最终编辑器展开的大类型,往往难以判断是哪一种机制引入了某个成员。
函数属性、方法声明、any 和 never 都可能影响更复杂推断。不要把一个示例中的联合或交叉结论当作所有位置的定律。对公共工具应验证典型输入、冲突候选、重载顺序和特殊类型,必要时改用更明确的类型参数关系。
容易答错的地方
- infer 遇到多个类型总是生成联合
- 候选所处的位置和结构关系会影响合并方式,输入位置可能要求交叉;还需区分候选合并与条件类型本身的联合分发。
- 提取重载返回类型会自动执行最佳重载匹配
- 没有具体调用参数时,这不是一次普通重载调用,通常从最后声明签名推断;需要输入输出映射时应显式设计相应契约。
面试官还会怎么问?
两个输入候选是 string 和 number 会怎样?
在这种交叉要求下可能得到 never,因为没有正常值同时满足两者;应确认工具真的是在寻找共同输入,而不是想收集任一可能输入。
调整重载顺序会影响工具类型结果吗?
可能会,因为最后可见签名会影响推断,公开声明的顺序也因此属于可观察类型契约;修改后应验证消费端代表性用法。
如何避免复杂 infer 推断难以维护?
优先拆出清楚的中间类型,减少一层同时承担分发、过滤与候选合并的职责,并给重要结果写有意义的类型断言测试。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。