先记住这个答案
TypeScript 可以在泛型函数调用和泛型类构造等位置,从实参结构、回调返回以及可用的上下文类型推断类型参数。没有足够证据时,结果可能退到约束、默认类型或 unknown 等形式,具体取决于签名与上下文。返回位置并非永远不能参与推断,但编译器也不会根据字符串内容或运行时请求结果猜出任意业务类型。普通类型别名的实例化通常仍需提供必需类型参数。遇到意外推断,应先检查证据在哪一步被拓宽或丢失,再决定补注解还是调整接口。
- 实参和上下文都可能提供泛型推断证据
- 无输入来源的泛型不能自动知道业务类型
- 显式参数应补充真实契约而非掩盖错误输入
映射函数如何同时推断两个参数
数组元素告诉编译器回调会收到什么,回调的返回表达式又告诉它结果元素是什么。这样调用方通常不必同时手写 T 与 U,回调参数还能够获得上下文提示。接口若把这些关联提前变成 any,推断能力就会随之减弱。
示例把字符串数组映射为长度数组,再对照没有实参的空列表工厂。孤立调用缺少元素信息,得到 unknown 数组;目标位置明确声明数字数组时,上下文可以帮助确定元素类型。这里的空数组实现对任意元素类型都成立,并没有凭空构造一个未知 T。
function mapValues<T, U>(values: readonly T[], project: (value: T) => U): U[] {
return values.map(project);
}
function empty<T>(): T[] { return []; }
const lengths = mapValues(['ab', 'c'], word => word.length);
const uncertain = empty();
const numbers: number[] = empty();
const explicit = empty<boolean>();lengths 保留数字元素类型,没有其他信息的 uncertain 是 unknown[],上下文中的 numbers 和显式选择的 explicit 分别表达数字与布尔数组。所有结果仍由真实函数体产生,类型推断不会填入元素。
为什么提前拓宽后难以恢复信息
一个对象字段已经声明为 string,再传给泛型函数,输入证据通常就是这个宽字符串,不能期待函数自动记起它最初只写过某个字面量。保留更细信息应在构造边界发生,例如明确有限联合或使用适当的字面量推断。
显式类型参数可以让契约更清楚,但不是自动变换输入的指令。指定数字类型后传入字符串仍应报错,指定更宽联合也可能主动丢掉精度。若只想锁定某个参数而让其余继续推断,还要检查当前签名的必需参数与默认规则是否支持。
不应依赖没有证据的泛型承诺
一个只接收 URL 的请求函数,其字符串内容通常无法让编译器推导服务端响应结构。把返回类型声明为任意 T,只是允许调用者选择期待类型,不代表函数已经验证响应。可靠接口可以接收解析器,或由明确的路由契约映射返回类型。
泛型类型别名与函数调用也不同。声明 Box<T> 后,单独在类型位置写 Box 通常不会根据旁边对象自动补齐必需参数,除非类型提供了默认值。排查推断问题时应先分清当前是在调用、构造、上下文检查还是类型实例化位置。
容易答错的地方
- 泛型只能从输入推断,返回上下文永远无效
- 某些调用的目标上下文确实能够提供候选或约束,不能用绝对规则概括;应针对具体签名和编译器查看实际推断结果。
- 编译器能根据请求地址猜出接口数据类型
- 普通字符串没有自动关联任意服务器协议,除非类型层明确建立了映射;无来源的泛型返回仍需要解析器或可信契约支持。
面试官还会怎么问?
unknown 推断出来是否说明编译器出错?
不一定,它可能准确表达当前没有足够信息;应补充真实输入、目标契约或明确默认,而不是为了获得属性提示直接改成 any。
泛型类实例化也能推断参数吗?
可以从构造器实参及可用上下文获取信息,但没有证据时同样会遇到宽类型或默认结果;类名本身不会携带具体业务数据类型。
为什么拆出回调后参数变成隐式 any?
原先内联回调可能从调用位置获得上下文类型,拆成独立声明后这个上下文不再直接提供参数信息,需要保留明确签名或重新组织关联。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。