深入解析 TypeScript 联合类型:从基础到实战应用

作者:闭包旅人

时间:2026-10-31

浏览:557

本文深入解析 TypeScript 中的联合类型,涵盖基本语法、字面量联合与对象联合的区别。重点讲解赋值与读取时的类型判断差异,以及如何通过类型收窄和可辨识联合(Discriminated Union)安全访问独有属性,并结合请求状态等实际场景提供最佳实践。

文章目录

1. 什么是联合类型

联合类型用于描述一个值可能属于多种类型中的某一种。

在 TypeScript 中,可以使用竖线符号 | 来连接多个类型,从而构建联合类型。

let value: string | number;

value = 'hello'; // ✅ string
value = 123;     // ✅ number
value = true;    // ❌ boolean 不在联合类型中

以 string | number 为例,这表示该变量的值既可以是字符串类型,也可以是数字类型。

在代码执行并确定具体值之前,TypeScript 编译器无法预知该变量当前究竟是字符串还是数字,因此需要谨慎处理。

2. 字面量联合

字面量联合允许我们限制变量只能取一组固定的特定值。

type Status = 'pending' | 'success' | 'error';

let status: Status = 'success'; // ✅
status = 'pending';             // ✅
status = 'loading';             // ❌ 不在可选值中

这种机制非常适合用于表示状态枚举、方向标识或尺寸规格等具有有限可选值的场景。

3. 对象联合

联合类型同样适用于对象类型,允许一个变量指向多种不同的对象结构。

type Student = {
  name: string;
  score: number;
};

type Teacher = {
  name: string;
  age: number;
  subject: string;
};

type Person = Student | Teacher;

对于对象联合,其含义并非将多个对象的属性进行简单合并,而是表示该变量符合其中某一种对象的结构定义。

例如,Person 类型可能代表 Student,也可能代表 Teacher,具体取决于实际赋值的对象。

因此,只要对象满足 Student 或 Teacher 中任意一种类型的定义,就可以赋值给 Person 类型的变量。

const student: Person = {
  name: '小明',
  score: 95,
};

const teacher: Person = {
  name: '张老师',
  age: 30,
  subject: '数学',
};

然而,如果一个对象缺少了 Student 和 Teacher 中任一类型所必需的属性,则无法赋值给 Person。

在给对象联合类型赋值时,必须确保该对象完整满足联合成员中至少一种类型的所有必选属性要求。

const person: Person = {
  name: '小红',
}; // ❌ 既不满足 Student,也不满足 Teacher

3.1 为什么只能直接读取公共属性

假设有一个函数接收 Person 类型的参数,我们需要理解其内部逻辑。

function printPerson(person: Person) {
  console.log(person.name);    // ✅ Student 和 Teacher 都有 name
  console.log(person.score);   // ❌ Teacher 没有 score
  console.log(person.subject); // ❌ Student 没有 subject
}

尽管运行时 person 的实际值必然是 Student 或 Teacher 中的一种,但在编写函数时,TypeScript 仅知道它是 Student | Teacher 的联合,无法确定具体是哪一种。

我们可以这样理解类型系统的约束机制。

属性Student 有吗Teacher 有吗能直接读取吗
name有有✅ 可以
score有没有❌ 不可以
age没有有❌ 不可以
subject没有有❌ 不可以

TypeScript 仅允许直接访问联合类型中所有成员共同拥有的属性,这样可以确保无论传入的是哪种具体对象,代码访问都不会引发类型错误。

3.2 赋值和读取的判断方式不同

这是对象联合使用中常见的混淆点,需要明确区分赋值与读取时的类型检查逻辑。

  • 在赋值阶段:只要对象满足联合成员中任意一种类型的定义,赋值即成功。
  • 在读取阶段:在未进行类型收窄之前,只能访问所有联合成员共有的属性。
const person: Person = {
  name: '小明',
  score: 95,
}; // ✅ 赋值时满足 Student

function show(person: Person) {
  person.name;  // ✅ 两种类型都有
  person.score; // ❌ 此处的 person 也可能是 Teacher
}

记住这一原则:

传入的对象可以是联合中的任何一种类型,但在未通过条件判断缩小范围前,只能按所有类型共有的属性来使用。

3.3 同时拥有两边的属性,为什么也能赋值

考虑这样一个对象,它同时包含了 Student 和 Teacher 所需的所有属性。

const person: Person = {
  name: '小王',
  score: 90,
  age: 28,
  subject: '英语',
}; // ✅

这是因为 TypeScript 采用结构类型系统:在判断一个值能否赋值给某个类型时,主要检查它是否具备该类型所要求的结构,多出的属性并不影响赋值的合法性。

该对象具备 Student 要求的 name 和 score 属性,因此它至少满足 Student 类型的定义,可以赋值给 Student | Teacher。

因此,A | B 中的“或者”是包含性的逻辑:

  • 仅满足 A 类型,赋值允许。
  • 仅满足 B 类型,赋值允许。
  • 同时满足 A 和 B 类型,赋值也允许。
  • 既不满足 A 也不满足 B,赋值不允许。

不过,当一个变量的类型被声明为 Person 时,不应依赖“这个值看起来两边属性都有”这种直觉。函数参数、接口返回值等位置的 Person 仍可能只是其中一种类型,因此在通用代码中,仍应先进行类型收窄再读取独有属性。

4. 类型收窄

如果想读取某个联合成员独有的属性,需要先进行判断,让 TypeScript 确定当前具体是哪一种类型。这个过程被称为类型收窄。

4.1 使用 in 判断属性

function printPerson(person: Person) {
  if ('score' in person) {
    // 这里 person 被收窄为 Student
    console.log(`学生 ${person.name} 的成绩是 ${person.score}`);
  } else {
    // 这里 person 被收窄为 Teacher
    console.log(`${person.name} 老师教授 ${person.subject}`);
  }
}

'score' in person 用于判断对象中是否存在 score 属性,从而推断其具体类型。

4.2 使用可辨识属性(推荐)

更常用且清晰的做法是,给每种对象添加一个同名但值不同的标记属性。

type Student = {
  role: 'student';
  name: string;
  score: number;
};

type Teacher = {
  role: 'teacher';
  name: string;
  subject: string;
};

type Person = Student | Teacher;

这里的 role 属性即作为区分不同对象类型的标记。

function printPerson(person: Person) {
  if (person.role === 'student') {
    console.log(person.score);   // ✅ person 是 Student
  } else {
    console.log(person.subject); // ✅ person 是 Teacher
  }
}

也可以使用 switch 语句进行类型判断。

function printPerson(person: Person) {
  switch (person.role) {
    case 'student':
      console.log(`${person.name} 的成绩是 ${person.score}`);
      break;
    case 'teacher':
      console.log(`${person.name} 教授 ${person.subject}`);
      break;
  }
}

这种写法被称为可辨识联合(Discriminated Union),常用于处理请求状态、表单状态或消息类型等场景。

5. 一个实际例子:请求状态

如果使用多个可选属性来描述请求状态,容易生成不合理的数据结构。

type RequestState = {
  status: 'loading' | 'success' | 'error';
  data?: string;
  message?: string;
};

例如,当 status 为 success 时,TypeScript 无法保证 data 属性一定存在,因为它是可选的。

使用对象联合可以让每种状态拥有自己必需的数据结构。

type RequestState =
  | { status: 'loading' }
  | { status: 'success'; data: string }
  | { status: 'error'; message: string };

function render(state: RequestState) {
  switch (state.status) {
    case 'loading':
      return '加载中...';
    case 'success':
      return `请求结果:${state.data}`;
    case 'error':
      return `请求失败:${state.message}`;
  }
}

这样可以从类型层面保证数据结构的完整性:

  • loading 状态不需要结果数据。
  • success 状态一定包含 data 属性。
  • error 状态一定包含 message 属性。

6. 联合类型和交叉类型的区别

不要将对象联合符号 | 与交叉类型符号 & 混淆。

type A = { a: string };
type B = { b: number };

type Union = A | B;
type Intersection = A & B;

它们对类型的要求截然不同:

const value1: Union = { a: 'hello' };             // ✅ 满足 A 即可
const value2: Union = { b: 100 };                 // ✅ 满足 B 即可
const value3: Intersection = { a: 'hi', b: 100 }; // ✅ 必须同时满足 A 和 B
  • A | B 表示类型是 A 或者 B。
  • A & B 表示类型同时是 A 并且 B。

7. 总结

  1. 联合类型表示一个值可能是多种类型中的一种。
  2. 对象联合 A | B 表示对象可以是 A,也可以是 B,而不是简单地合并属性。
  3. 给联合类型赋值时,满足其中一种类型即可。
  4. 使用联合类型时,默认只能读取所有成员共有的属性。
  5. 判断出具体类型后,才能读取该类型独有的属性。
  6. 对象联合推荐添加 role、type、status、kind 等可辨识属性。

最重要的一句话:

对象联合描述的是“这个对象可能是哪一种”;类型收窄负责判断“它现在到底是哪一种”。

到此这篇关于深入了解TypeScript 联合类型的文章就介绍到这了,更多相关TypeScript 联合类型内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章