TypeScript中实现互斥类型(XOR)的完整指南

作者:纸飞机时间:2026-10-25 08:44:03浏览:452

本文详解如何在TypeScript中定义互斥类型,确保对象属性中仅有一个存在。通过结合Never类型、Exclude工具类型及泛型,构建通用的XOR类型解决方案,并深入解析其编译期报错原理与测试用例。

前言

假设存在一个对象,包含name和title两个属性。在赋值时,这两个属性必须互斥,即出现name时title不可出现,反之亦然。

面对这一需求,如何利用TypeScript进行类型定义?本文将介绍一种互斥类型的实现方案,供开发者参考。

前置知识

在深入实现之前,需掌握以下基础概念。

对象中多属性同类型的定义

若对象包含a、b、c、d、e五个可选属性,且类型均为string,常规写法如下。

type obj = {
  a?:string;
  b?:string;
  c?:string;
  d?:string;
  e?:string;
}

是否存在更简洁的定义方式?答案是肯定的,具体实现如下。

type obj = { [P in "a" | "b" | "c" | "d" | "e"]?: string };

never类型

TypeScript中的never类型是所有类型的子类型,无法再被细分。这意味着除了never本身,没有任何其他类型可以赋值给它。

通过示例说明:

  • 定义变量amazing并赋予never类型。
  • 尝试赋予number、boolean、string、object或array等不同类型的值,编译均会失败,因为never类型无法容纳任何具体值。
let amazing: never;
amazing = 12;// 报错:amazing是never类型不能分配给number类型
amazing = true;// 报错:amazing是never类型不能分配给boolean类型
amazing = "真神奇";// 报错:amazing是never类型不能分配给string类型
amazing = {};// 报错:amazing是never类型不能分配给{}类型
amazing = [];// 报错:amazing是never类型不能分配给[]类型

剔除联合类型中的属性

对于联合类型"a" | "b" | "c" | "d",若需剔除b和c,可使用TypeScript内置的Exclude工具类型。该函数接收两个参数:

  • UnionType:联合类型
  • ExcludedMembers:需要剔除的属性集合

具体用法如下。

type P = Exclude<"a" | "b" | "c" | "d", "b" | "c"> // "a" | "d"

将对象所有属性转为联合类型

若需将包含name和title两个可选属性的对象转为联合类型name | title,可使用keyof工具类型。具体用法如下。

type A =  { [P in "name" | "title"]?: string };

type UnionType = keyof A; // "name" | "title"

实现互斥类型

基于上述知识,我们可以构建互斥类型,解决文章开头提出的属性互斥问题。

实现思路梳理如下:

  • 首先定义排除类型,从对象A中剔除对象B的属性,并将剩余属性类型设为never,生成新对象类型。
  • 基于排除类型构建互斥类型,将对象A和B代入排除逻辑,彼此剔除对方属性,并通过联合运算符连接结果。

核心原理即通过部分属性设为never来实现互斥约束。

实现代码

具体代码实现如下。

// 定义排除类型:将U从T中剔除, keyof 会取出T与U的所有键, 限定P的取值范围为T中的所有键, 并将其类型设为never
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };

// 定义互斥类型,T或U只有一个能出现(互相剔除时,被剔除方必须存在)
type XOR<T, U> = (Without<T, U> & U) | (Without<U, T> & T);

为确保类型可复用,代码使用了泛型。不熟悉泛型的开发者可参考TypeScript官方文档中关于泛型的章节。

测试用例

将文章开头的场景代入上述代码,验证其有效性。

// A类型
type A = {
  name: string;
};

// B类型
type B = {
  title: string;
};

// A和B两种类型只有一个能出现
type AOrB = XOR<A, B>;

// 传值测试
const AOrB1: AOrB = { name: "姓名" }; // 编译通过
const AOrB2: AOrB = { title: "标题" }; // 编译通过
const AOrB3: AOrB = { title: "标题", name: "姓名" }; // 报错: Type '{ title: string; name: string; }' is not assignable to type 'AOrB'.
const AOrB4: AOrB = { name: "姓名", otherKey: "" }; // 报错:Type '{ name: string; otherKey: string; }' is not assignable to type 'AOrB'.

当两个属性同时赋值时,编译器会抛出类型错误。这是因为被剔除的属性类型被设为never,赋予任何值均会导致类型不匹配。

前言

用例拆解

部分开发者可能对测试用例感到困惑,虽前置知识已讲解各部分,但组合后可能难以理解。以下将详细拆解代码逻辑。

type AOB = ({ name?: never } & {
      title: string;
    }) | ({ title?: never } & {
      name: string;
    });

// 传值测试
const a: AOB = { name: "姓名" }; // 编译通过
const b: AOB = { title: "标题" }; // 编译通过
const c: AOB = { title: "标题", name: "姓名" }; // 报错
const d: AOB = { title: "标题", otherKey: "" }; // 报错

建议开发者在编辑器中亲自编写测试,若仍不理解,可先收藏本文,日后深入学习。

写在最后

本文到此结束。

至此,关于TypeScript中互斥类型实现的文章介绍完毕。如需了解更多TypeScript互斥类型相关内容,请查阅本站过往文章或继续阅读下文。感谢大家的支持!

作者最新文章
相关文章
更多