为何TypeScript此处丢失类型推断并默认转为'any'类型?
问题场景
在TypeScript 5.4.2中,通过条件类型根据泛型参数是否为string定义可选/必填的回调属性时,传入联合类型{ hello: string } | string作为泛型参数后,回调参数被隐式推断为any,触发TS7006错误。
原始代码
type Optional<TItem> = TItem extends string ? { get?: (item: TItem) => string } : { get: (item: TItem) => string }; type TItem = { hello: string } | string; const obj: Optional<TItem> = { get: item => typeof item === 'string' ? item : item.hello };
编译错误
执行npx tsc后输出:
error TS7006: Parameter 'item' implicitly has an 'any' type. 18 get: item => typeof item === 'string' ? item : item.hello
原因解析
这并非TypeScript的Bug,而是分布式条件类型的特性导致的。当泛型参数为联合类型时,条件类型会对联合类型的每个成员分别进行判断,再将结果合并为新的联合类型。
对于Optional<TItem>,TItem是{ hello: string } | string,条件类型会拆分处理两个成员:
- 当成员为
string时,得到{ get?: (item: string) => string } - 当成员为
{ hello: string }时,得到{ get: (item: { hello: string }) => string }
最终Optional<TItem>的类型是这两个结果的联合:
{ get?: (item: string) => string } | { get: (item: { hello: string }) => string }
在该联合类型下,TypeScript无法确定get回调的参数到底是string还是{ hello: string },因此无法正确推断参数类型,只能隐式设为any。
解决方案
新增一个独立的类型参数TItemType,将原本的分布式条件类型转换为非分布式判断(条件判断针对独立泛型参数TItemType,而非直接拆分联合类型TItem),避免联合类型被拆分处理,让TypeScript能正确推断回调参数的类型为原联合类型TItem。
修改后的代码:
type TItem = { hello: string } | string; type Optional<TItem, TItemType> = TItemType extends string ? { get?: (item: TItem) => string } : { get: (item: TItem) => string }; const obj: Optional<TItem, TItem> = { get: item => typeof item === 'string' ? item : item.hello };
此时Optional<TItem, TItem>的条件判断针对整个联合类型TItem是否属于string的子类型(显然联合类型并不完全符合),因此会走else分支,得到{ get: (item: TItem) => string },回调参数item的类型被正确推断为{ hello: string } | string,可正常进行类型守卫判断。
内容的提问来源于stack exchange,提问作者nepellion

