为什么这段代码能让TypeScript从父函数推断泛型类型?
特性本质
你提到的「子函数泛型从父函数参数反推」是TypeScript有意设计的标准特性,属于「上下文类型(Contextual Typing)」推导规则的一部分,并不是未定义的野特性。
TypeScript的泛型推导不是单向的,除了从函数实参往返回值推导之外,当一个值的类型被它所处的上下文环境(比如父函数的参数类型要求)明确约束时,会反向推导该值内部的未确定泛型参数。
两个示例的差异原理
你给出的两个update函数表现不同,核心差异在于泛型T的推导时机:
不生效的版本
function update<T>(u: Partial<T>) { return (t: T) => ({ ...t, ...u }) }
当你调用update({ foo: 1 })时,TypeScript会优先从实参推导泛型:参数u的类型是Partial<T>,传入的实参是{ foo: 1 },这时候T会直接被推导为{ foo: number },后续你把返回的函数传给apply<Model>时,类型已经固定为(t: {foo: number}) => {foo: number},和apply要求的(t: Model) => Model不匹配,自然会报错。
生效的版本
function update<T>(u: T extends infer U ? Partial<U> : never) { return (t: T) => ({ ...t, ...u }) };
这里用到的T extends infer U ? Partial<U> : never属于延迟解析的条件类型:只要条件类型中存在未确定的泛型参数,TypeScript就不会提前解析这个类型的具体形态。
所以当你调用update({ foo: 1 })时,TS无法从参数u的类型反推T的具体值,只能暂时挂起T的推导。这时候你把返回值传给apply<Model>,apply明确要求传入的函数类型是(t: Model) => Model,和update返回的(t: T) => T匹配时,就会反向推导出T必须是Model。确定T的类型后,TS再回头校验你传入的{ foo: 1 }是否符合Partial<Model>,校验通过自然不会报错。
是否为有意设计
这个特性是TypeScript类型系统的核心设计之一,上下文类型反向推导的逻辑广泛应用在事件回调、JSX属性、泛型函数传参等场景中,你用到的「延迟条件类型阻塞提前推导」也是官方支持的泛型技巧,常用于需要让泛型优先从上下文推导、而非从实参推导的场景。
内容的提问来源于stack exchange,提问作者David Shortman

