You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 02:10:53