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

TypeScript中嵌套联合类型展开及类型兼容性校验问题

TypeScript中嵌套联合类型展开及类型兼容性校验问题

嘿,这个问题我之前也琢磨过好一会儿,刚好能给你理清楚来龙去脉~

首先解决你第一个核心需求:把嵌套的联合类型展开成所有可能的组合联合。我们可以写一个递归的类型工具,利用TypeScript的条件类型分发特性来实现:

// 递归展开所有嵌套的联合类型,生成全组合的联合类型
type ExpandNestedUnions<T> = 
  T extends object 
    ? { [K in keyof T]: ExpandNestedUnions<T[K]> } extends infer U
      ? U extends Record<string, any>
        ? { [K in keyof U]: U[K] } extends infer V
          ? V extends any
            ? { [K in keyof V]: V[K] }
            : never
          : never
        : never
      : never
    : T;

// 测试你的示例类型
type Obj = { a: 1 | 2, b: { c: 3 | 4 } };
type ExpandedObj = ExpandNestedUnions<Obj>;
// 此时 ExpandedObj 就是你想要的:
// { a: 1, b: { c: 3 } } | { a: 1, b: { c: 4 } } | { a: 2, b: { c: 3 } } | { a: 2, b: { c: 4 } }

这个工具类型的核心是递归遍历对象的每个属性,把嵌套的联合类型逐层展开,最后利用TypeScript的条件类型自动分发特性,生成所有可能的对象组合。

接下来解答你疑惑的点:为什么{ inner: { tag: 'a' | 'b' } } extends { inner: { tag: 'a' } } | { inner: { tag: 'b' } }不成立?

这其实是TypeScript类型兼容性规则的细节差异:

  • 对于单一属性类型{ tag: 'a' | 'b' } extends { tag: 'a' } | { tag: 'b' },TypeScript会检查前者的每个可能值是否能被后者的某个成员接受——显然'a'和'b'分别匹配右边的两个类型,所以这个判断成立。
  • 但嵌套到对象后,{ inner: { tag: 'a' | 'b' } }是一个属性类型为联合的单一对象类型,而右边的{ inner: { tag: 'a' } } | { inner: { tag: 'b' } }是两个单一属性对象的联合类型。TypeScript认为前者的实例可能包含一个动态变化的inner值(比如运行时才确定是'a'还是'b'),而后者的实例的inner值是固定的,因此前者不能安全地赋值给后者,所以extends判断不成立。

那有没有替代的条件检查方法?
如果你的核心需求是验证嵌套联合类型的所有可能组合是否被目标联合覆盖,那可以用我们刚才的ExpandNestedUnions工具,先把左边的类型展开成全组合联合,再检查这个展开后的类型是否extends目标类型:

type IsAllCasesCovered<T, U> = ExpandNestedUnions<T> extends U ? true : false;

// 测试你的场景
type Target = { inner: { tag: 'a' } } | { inner: { tag: 'b' } };
type Source = { inner: { tag: 'a' | 'b' } };
type CheckResult = IsAllCasesCovered<Source, Target>; // 结果为 true

如果你的场景更简单,只是想验证内层联合类型的覆盖性,那直接检查内层类型的extends关系就可以了,不需要嵌套外层对象做判断,这样更直接也符合TypeScript的类型规则。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 10:54:34