在 TypeScript 类型系统中,联合类型(Union Types)与交叉类型(Intersection Types)是处理复杂类型组合的核心机制。联合类型用于表示值可能属于多种类型中的一种,体现"或"关系;交叉类型则用于将多个类型合并为一个新类型,体现"且"关系。这两种类型构造方式在处理用户输入验证、对象组合等场景中具有重要应用价值。
联合类型通过竖线(|)分隔多个类型构成,表示取值可以是这些类型中的任意一种。在用户输入处理场景中,当输入源可能提供字符串、数字或布尔值等多种类型数据时,联合类型能够有效描述这种不确定性。例如,定义 input: string | number | boolean 表示输入值可以是字符串、数字或布尔值中的任意一种。
类型守卫机制:为了安全地操作联合类型的值,需要通过类型守卫(Type Guards)缩小类型范围。TypeScript 提供三种主要类型守卫方式:
typeof 操作符:用于区分基本类型(string/number/boolean等)instanceof 操作符:用于区分对象类型(如 Date/Array等)arg is Type 形式的函数实现复杂类型判断以下代码展示了用户输入处理的完整流程,包括类型判断、错误处理和结果转换:
// 定义联合类型输入
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(数字)属性。
交叉类型的合并规则遵循:
never 类型以下示例展示了交叉类型的基本用法和冲突处理机制:
// 基础类型交叉
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 函数,接收用户基本信息或联系方式,返回包含完整信息的用户对象。
// 定义基础类型
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 高级类型特性的基础。