如何从数组联合类型中正确推导分发式类型以解决TypeScript类型不兼容问题
我理解你的痛点——你无法修改getData的返回类型(它返回Foo[] | Bar[]而非(Foo | Bar)[]),但TypeScript无法自动将数组联合类型推断为元素联合的数组,导致泛型函数myFunc出现类型不兼容错误,且renderRow的参数被错误推断为Foo而非Foo | Bar。下面结合你提到的分布式条件类型,给出三种针对性解决方案:
方案一:手动指定泛型参数为元素联合类型
这是最直接的临时解决办法:直接给myFunc指定泛型参数为Foo | Bar,让TypeScript将data的类型视为(Foo | Bar)[](而Foo[] | Bar[]本身是可以安全赋值给(Foo | Bar)[]的,因为每个数组元素都属于Foo | Bar范畴):
type Foo = { id: string; someFooProp: string }; type Bar = { id: string; someBarProp: string }; function getData(): Foo[] | Bar[] { const bars: Bar[] = [{id: '1', someBarProp: '1'}]; return bars; } type MyFuncProps<T> = { data: T[]; renderRow: (data: T) => string; }; function myFunc<T>(props: MyFuncProps<T>) { return { /* iterate over data and render*/ }; } // 手动指定泛型参数为 Foo | Bar myFunc<Foo | Bar>({ data: getData(), // 类型错误消失,TypeScript 允许 Foo[] | Bar[] 赋值给 (Foo | Bar)[] renderRow: (dataItem) => { if ('someBarProp' in dataItem) return 'this is bar'; if ('someFooProp' in dataItem) return 'this is foo'; return ''; } });
优点是快速见效,缺点是需要手动维护Foo | Bar这个联合类型,后续类型变更时需要同步更新。
方案二:用分布式条件类型自动推导元素联合类型
如果你希望TypeScript自动从数组联合类型中提取元素的联合,可以借助分布式条件类型实现自动化推导,无需手动指定泛型:
步骤1:定义元素类型提取工具
先创建一个工具类型,它会利用TypeScript的类型推断和分布式特性,从数组类型中提取元素类型:
// 分布式条件类型:从数组类型中提取元素类型 type ElementType<T> = T extends (infer U)[] ? U : never;
当T是联合类型(比如Foo[] | Bar[])时,TypeScript会自动将联合类型拆分,分别对每个成员执行条件判断,最终返回Foo | Bar。
步骤2:调整泛型函数的约束
修改myFunc的泛型定义,让它接受任意数组类型,并自动绑定renderRow的参数类型为数组元素的联合类型:
type Foo = { id: string; someFooProp: string }; type Bar = { id: string; someBarProp: string }; function getData(): Foo[] | Bar[] { const bars: Bar[] = [{id: '1', someBarProp: '1'}]; return bars; } // 提取数组元素类型的工具类型 type ElementType<T> = T extends (infer U)[] ? U : never; // 重新定义泛型函数:接受任意数组类型 T,自动推导元素类型 function myFunc<T extends any[]>(props: { data: T; renderRow: (dataItem: ElementType<T>) => string; }) { return { /* iterate over data and render*/ }; } myFunc({ data: getData(), // 自动推导 T 为 Foo[] | Bar[] renderRow: (dataItem) => { // dataItem 被正确推断为 Foo | Bar if ('someBarProp' in dataItem) return 'this is bar'; if ('someFooProp' in dataItem) return 'this is foo'; return ''; } });
核心原理
ElementType<T>是典型的分布式条件类型:当T为联合类型时,TypeScript会将联合类型拆分,对每个成员单独执行条件判断,最终将结果合并为新的联合类型。对于Foo[] | Bar[],它会被拆分为:
ElementType<Foo[]> | ElementType<Bar[]> = Foo | Bar
这就自动从数组联合类型中提取出了元素的联合类型,完美匹配你的需求。
方案三:保留原MyFuncProps结构的优化
如果你想保留原有的MyFuncProps泛型结构,可以通过包装泛型参数实现自动推导:
type Foo = { id: string; someFooProp: string }; type Bar = { id: string; someBarProp: string }; function getData(): Foo[] | Bar[] { const bars: Bar[] = [{id: '1', someBarProp: '1'}]; return bars; } type ElementType<T> = T extends (infer U)[] ? U : never; // 调整 MyFuncProps 接受数组类型,通过 ElementType 推导元素类型 type MyFuncProps<TArr> = { data: TArr; renderRow: (data: ElementType<TArr>) => string; }; function myFunc<TArr extends any[]>(props: MyFuncProps<TArr>) { return { /* iterate over data and render*/ }; } myFunc({ data: getData(), renderRow: (dataItem) => { if ('someBarProp' in dataItem) return 'this is bar'; if ('someFooProp' in dataItem) return 'this is foo'; return ''; } });
这个方案和方案二原理一致,只是将类型定义拆分成了MyFuncProps,保持了你原有的代码结构。
内容来源于stack exchange

