联合类型与交叉类型

在 TypeScript 类型系统中,联合类型(Union Types)与交叉类型(Intersection Types)是处理复杂类型组合的核心机制。联合类型用于表示值可能属于多种类型中的一种,体现"或"关系;交叉类型则用于将多个类型合并为一个新类型,体现"且"关系。这两种类型构造方式在处理用户输入验证、对象组合等场景中具有重要应用价值。

联合类型:处理多类型输入场景

联合类型通过竖线(|)分隔多个类型构成,表示取值可以是这些类型中的任意一种。在用户输入处理场景中,当输入源可能提供字符串、数字或布尔值等多种类型数据时,联合类型能够有效描述这种不确定性。例如,定义 input: string | number | boolean 表示输入值可以是字符串、数字或布尔值中的任意一种。

类型守卫机制:为了安全地操作联合类型的值,需要通过类型守卫(Type Guards)缩小类型范围。TypeScript 提供三种主要类型守卫方式:

  • typeof 操作符:用于区分基本类型(string/number/boolean等)
  • instanceof 操作符:用于区分对象类型(如 Date/Array等)
  • 自定义类型谓词:通过返回 arg is Type 形式的函数实现复杂类型判断

以下代码展示了用户输入处理的完整流程,包括类型判断、错误处理和结果转换:

typescript
复制代码
// 定义联合类型输入
type InputType = string | number | boolean | undefined;

function processInput(input: InputType): string {
  // 处理可能为undefined的情况
  if (input === undefined) {
    throw new Error("输入不能为空");
  }

  // 使用typeof进行基本类型守卫
  if (typeof input === "string") {
    return `字符串输入: ${input.toUpperCase()}`;
  } else if (typeof input === "number") {
    return `数字输入: ${input.toFixed(2)}`;
  } else if (typeof input === "boolean") {
    return `布尔输入: ${input ? "true" : "false"}`;
  }

  // 穷尽性检查,确保所有类型都被覆盖
  const _exhaustiveCheck: never = input;
  throw new Error(`无法处理的输入类型: ${_exhaustiveCheck}`);
}

// 错误示例:访问联合类型不存在的属性
function invalidAccessExample(input: string | number) {
  // 编译错误:Property 'toUpperCase' does not exist on type 'number'
  input.toUpperCase(); 
}

上述代码中,通过逐步缩小类型范围,确保在每个条件分支中只能访问该类型存在的属性和方法。特别加入了穷尽性检查(Exhaustive Check),当新增联合类型成员而未处理时,TypeScript 会在编译阶段抛出错误,提高代码健壮性。

交叉类型:实现对象类型合并

交叉类型通过与符号(&)连接多个类型构成,表示同时具备所有类型的特征。在对象合并场景中,交叉类型能够将多个对象类型的属性组合为一个新类型。例如,type User = { name: string } & { age: number } 表示 User 类型同时拥有 name(字符串)和 age(数字)属性。

交叉类型的合并规则遵循:

  1. 属性叠加:不同属性会被合并到新类型中
  2. 冲突处理:同名属性若类型兼容则保留,若不兼容则结果为 never 类型

以下示例展示了交叉类型的基本用法和冲突处理机制:

typescript
复制代码
// 基础类型交叉
type NumberAndString = number & string; // 结果为 never 类型(无交集)

// 对象类型交叉
type NameInfo = { name: string };
type AgeInfo = { age: number };
type ContactInfo = { email: string };

// 属性叠加:合并所有属性
type User = NameInfo & AgeInfo & ContactInfo;
// User类型实际为 { name: string; age: number; email: string }

// 冲突属性处理
type A = { id: string };
type B = { id: number };
type ConflictType = A & B; // { id: never }(string与number无交集)

// 对象合并函数实现
function mergeObjects<T, U>(obj1: T, obj2: U): T & U {
  return { ...obj1, ...obj2 };
}

const userData = mergeObjects({ name: "Alice" }, { age: 30 });
// userData类型为 { name: string } & { age: number }
console.log(userData.name); // "Alice"(类型安全访问)
console.log(userData.age);  // 30(类型安全访问)

当交叉类型包含基本类型时,由于基本类型间通常无交集,结果会是 never 类型。这种特性可用于表示逻辑上不可能存在的类型,在错误处理和边界检查中非常有用。

#的核心区别

联合类型与交叉类型在类型关系和应用场景上存在本质区别,可通过以下对比清晰区分:

| 特征 | 联合类型(A | B) | 交叉类型(A & B) |
|------------------|-------------------------------------|-------------------------------------|
| 逻辑关系 | 或(OR)关系:值为A类型B类型 | 且(AND)关系:值同时为A类型B类型 |
| 类型范围 | 扩大类型取值范围 | 缩小类型取值范围 |
| 属性访问 | 只能访问所有类型共有的属性 | 可以访问所有类型的属性 |
| 典型应用 | 处理多类型输入、可选值场景 | 合并对象类型、组合接口定义 |
| 与never关系 | 联合never仍为原类型(A | never = A)| 交叉never结果为never(A & never = never)|

综合实践:联合类型参数转交叉类型结果

以下练习要求实现一个函数,接收联合类型参数,返回交叉类型结果。具体需求:创建 combineUserInfo 函数,接收用户基本信息或联系方式,返回包含完整信息的用户对象。

typescript
复制代码
// 定义基础类型
type BasicInfo = { id: number; name: string };
type ContactInfo = { email: string; phone: string };
type UserInfo = BasicInfo | ContactInfo;

/**
 * 接收联合类型参数,返回交叉类型结果
 * @param info 用户基本信息或联系方式
 * @param补充信息 需补充的另一部分信息
 * @returns 完整用户信息(BasicInfo & ContactInfo)
 */
function combineUserInfo(
  info: UserInfo,
 补充信息: UserInfo
): BasicInfo & ContactInfo {
  // 类型守卫:判断是否为BasicInfo
  const isBasicInfo = (data: UserInfo): data is BasicInfo => {
    return "id" in data && "name" in data;
  };

  // 根据类型组合结果
  if (isBasicInfo(info) && !isBasicInfo(补充信息)) {
    return { ...info, ...补充信息 };
  } else if (!isBasicInfo(info) && isBasicInfo(补充信息)) {
    return { ...补充信息, ...info };
  } else {
    throw new Error("必须提供不同类型的用户信息");
  }
}

// 测试用例
const basic: BasicInfo = { id: 1, name: "Bob" };
const contact: ContactInfo = { email: "bob@example.com", phone: "123456" };

const fullUser = combineUserInfo(basic, contact);
console.log(fullUser); 
// { id: 1, name: "Bob", email: "bob@example.com", phone: "123456" }

该实现通过自定义类型谓词 isBasicInfo 判断输入类型,确保只能组合不同类型的用户信息,最终返回交叉类型结果。这种模式在表单数据合并、分步信息收集等场景中具有实际应用价值。

通过联合类型与交叉类型的组合使用,可以构建出既灵活又安全的类型系统,有效提升 TypeScript 项目的代码质量和可维护性。理解这两种类型构造方式的核心差异和应用场景,是掌握 TypeScript 高级类型特性的基础。