TypeScript联合转交叉类型拆分后结果不一致问题问询
联合转交叉类型:单步与拆分实现结果差异的原因
先看两种实现的代码对比:
单步实现(正确得到交叉类型)
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; // 测试:UnionToIntersection<'foo' | 'bar'> → never(因为'foo'&'bar'无重叠,结果为never)
拆分多步实现(得到联合类型)
// 第一步:把联合类型转为函数联合 type UnionToFnUnion<U> = U extends any ? (k: U) => void : never; // 第二步:从函数类型提取参数 type FnToParam<F> = F extends ((k: infer I) => void) ? I : never; // 组合使用 type UnionToIntersectionSplit<U> = FnToParam<UnionToFnUnion<U>>; // 测试:UnionToIntersectionSplit<'foo' | 'bar'> → 'foo' | 'bar'
核心差异:分布式条件类型的触发时机
TypeScript的分布式条件类型规则是:当条件类型的extends左侧是一个裸类型参数(比如单独的U),且该参数是联合类型时,会自动将联合拆分为单个类型分别处理,最后把结果合并为联合。
单步实现:
左边的(U extends any ? (k: U) => void : never)确实会分布式展开为函数联合((k: 'foo') => void) | ((k: 'bar') => void),但接下来的extends ((k: infer I) => void)是对整个函数联合进行判断,而非单个函数类型。这里利用了函数参数的逆变特性:当一个函数联合要兼容某个函数类型时,其参数类型必须是联合中每个函数参数的交叉类型(只有交叉类型的参数能同时满足每个函数的参数要求)。所以TypeScript会推断I为'foo' & 'bar',也就是never。拆分实现:
第一步UnionToFnUnion已经把联合拆成了函数联合。第二步FnToParam的F extends ((k: infer I) => void)中,F是函数联合类型且是裸类型参数,所以会触发分布式条件类型——对每个函数类型单独提取参数,得到'foo'和'bar',最后合并为联合类型'foo' | 'bar'。
简单说,单步实现是把函数联合作为整体进行逆变推断,拆分实现则是对每个函数单独提取参数再联合,结果自然不同。
内容的提问来源于stack exchange,提问作者stratis
相关产品推荐
相关产品推荐

