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

TypeScript如何实现无冗余的多类型互斥联合类型?

TypeScript 无冗余实现互斥联合类型

我在TypeScript中定义了如下接口:

interface IPositionChange {
    previousIndex: number;
    newIndex: number;
}

interface IVisibilityChange {
    index: number;
    panel: string;
}

interface IAction {
    action: 'paint' | 'clean';
}

interface IActionChange extends IAction, IVisibilityChange { }

我需要定义一个互斥联合类型IChange,要求它的实例只能属于IPositionChange、IVisibilityChange、IActionChange中的某一种,不能同时包含多种类型的属性。但我尝试的几种IChange定义方式都没达到效果:在函数thisShouldNotCompile中,混合多种类型属性的赋值没有触发编译错误。

我发现给每个接口手动添加其他类型的never属性可以实现互斥,但这种方式代码冗余严重,后续扩展非常麻烦:

interface IPositionChange {
  previousIndex: number;
  newIndex: number;
  action: never;
  index: never;
  panel: never;
}

interface IVisibilityChange {
  index: number;
  panel: string;
  previousIndex: never;
  newIndex: never;
  action: never;
}

interface IAction {
  action: 'paint' | 'clean';
  index: never;
  panel: never;
  previousIndex: never;
  newIndex: never;
}

问题分析:普通联合类型为什么不生效?

TypeScript的普通联合类型(比如type IChange = IPositionChange | IVisibilityChange | IActionChange)的检查逻辑是只要对象满足联合类型中某一个成员的必填属性要求,就会被判定为合法,即使它包含其他成员的属性。

举个例子:一个同时包含previousIndex、newIndex、index的对象,既满足IPositionChange的要求(有previousIndex和newIndex),也满足IVisibilityChange的要求(有index和panel),所以TypeScript会认为它属于IChange,不会触发编译错误——这就是核心问题所在。


无冗余的互斥联合类型实现方案

可以通过工具类型自动生成互斥约束,不需要手动修改原接口,扩展性更强:

  1. 先定义基础的联合类型:
type BaseChange = IPositionChange | IVisibilityChange | IActionChange;
  1. 实现一个工具类型,自动给联合类型的每个成员添加其他成员属性的never约束:
type ExclusifyUnion<T, U = T> = T extends unknown 
  ? T & Partial<Record<Exclude<keyof U, keyof T>, never>> 
  : never;
  1. 用这个工具类型生成最终的IChange:
type IChange = ExclusifyUnion<BaseChange>;

原理说明

  • 分布式条件类型T extends unknown会遍历联合类型BaseChange的每个成员;
  • Exclude<keyof U, keyof T>会取出当前成员T以外的所有其他成员的属性名;
  • Record<..., never>把这些属性名映射为never类型,Partial让它们变为可选,但never类型不接受任何赋值,所以只要对象包含其他类型的属性,就会触发编译错误;
  • 最后通过交叉类型T & ...,把当前成员的属性和其他成员的never属性合并,实现互斥。

验证效果

现在再写混合属性的代码,就会触发编译错误:

function thisShouldNotCompile() {
  // 以下代码会报错:类型“number”不能赋值给类型“never”
  const invalidChange: IChange = {
    previousIndex: 0,
    newIndex: 1,
    index: 2 // 这里会报错,因为IPositionChange对应的互斥类型中index是never
  };
}

内容的提问来源于stack exchange,提问作者Mario Figueiredo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 20:36:17