首页/9

9章:高级类型:让类型更灵活

联合类型、交叉类型、类型守卫——类型系统的进阶招式

50分钟
4个学习目标

学习目标

  • 1掌握类型别名和联合类型
  • 2理解交叉类型和字面量类型
  • 3学会使用类型守卫安全地处理不同类型
  • 4掌握可辨识联合类型

上一章回顾:代码有了保障,但类型还不够灵活

在上一章中,我们学会了给博客项目加上测试和部署流程。代码质量有了保障,可以放心地修改和发布。

但你可能遇到了这样的问题:

博客有一个搜索功能,用户可以按关键词搜索,也可以按分类筛选,还可以同时使用。你该怎么定义参数类型?

// 只能按关键词搜索
function search(keyword: string): Post[] { ... }

// 只能按分类筛选
function filterByCategory(category: string): Post[] { ... }

// 既能搜索又能筛选?类型怎么定义?
function searchAndFilter(???) { ... }

这就是本章要解决的问题——让类型更灵活

类比一下:

之前学的类型就像"固定菜单"——每道菜的配料都是定好的。但高级类型就像"自助餐"——你可以自由组合,想吃什么拿什么,但仍然有规则(不能把甜品放进火锅里)。

类型别名:给类型起个好名字

类型别名用 type 关键字,可以给复杂的类型起一个简洁的名字。

为什么需要类型别名?

想象你的博客有这些类型:

// 没有类型别名——到处写重复的类型
function getUser(id: number): { id: number; name: string; email: string } { ... }
function createUser(data: { name: string; email: string }): { id: number; name: string; email: string } { ... }

// 有了类型别名——简洁明了
type User = { id: number; name: string; email: string };
function getUser(id: number): User { ... }
function createUser(data: Omit<User, 'id'>): User { ... }

类比一下:

类型别名就像给朋友起外号。你不用每次都叫"那个住在三楼的戴眼镜的高个子男生",直接叫"小明"就行了。

类型别名实战

typescript
加载中...

联合类型:这个变量可以是多种类型

联合类型用 | 连接多个类型,表示"可以是A,也可以是B"。

博客中的实际场景:

文章ID可能是数字(数据库自增),也可能是字符串(UUID):

type PostID = number | string;

function getPost(id: PostID): Post {
  // 可以传数字,也可以传字符串
}

文章状态只能是几个固定值之一:

type PostStatus = 'draft' | 'published' | 'archived';

function updateStatus(post: Post, status: PostStatus): Post {
  // 只能传这三个值,传别的会报错
}

类比一下:

联合类型就像"或"的关系——"今天中午吃米饭面条"。你只能选一个,但两个都是合法的选择。

联合类型实战

typescript
加载中...

交叉类型:把多个类型合并在一起

交叉类型用 & 连接多个类型,表示"必须同时满足A和B"。

博客中的实际场景:

用户基本信息和用户权限是分开的,但创建管理员时需要同时具备:

type BasicUser = { name: string; email: string };
type Admin = { role: 'admin'; permissions: string[] };

// 管理员 = 基本信息 + 管理权限
type AdminUser = BasicUser & Admin;

const admin: AdminUser = {
  name: '管理员',
  email: 'admin@example.com',
  role: 'admin',
  permissions: ['delete_post', 'edit_user'],
};

类比一下:

交叉类型就像"且"的关系——"申请奖学金需要成绩好社团活跃"。两个条件都必须满足。

交叉类型实战

typescript
加载中...

类型守卫:安全地处理不同类型

当你使用联合类型时,TypeScript不知道变量具体是哪种类型。类型守卫帮你"收窄"类型范围。

问题场景:

function processValue(value: string | number) {
  // 这里 value 可能是 string,也可能是 number
  // value.toUpperCase(); // 报错!number 没有 toUpperCase
  // value.toFixed(2);    // 报错!string 没有 toFixed
}

解决方案:

用类型守卫告诉TypeScript"在这个分支里,我确定它是某种类型"。

类比一下:

类型守卫就像机场安检——你必须出示证件(类型检查),安检员才会放行(类型收窄),让你进入对应的候机厅(代码分支)。

三种类型守卫

typescript
加载中...

可辨识联合:最强大的类型守卫模式

可辨识联合(Discriminated Unions)是TypeScript中最强大的类型模式之一。它通过一个共同的"标签"属性来区分不同类型。

博客中的实际场景:

博客的通知系统有多种通知类型:

type Notification =
  | { type: 'comment'; postId: number; commentText: string }
  | { type: 'like'; postId: number; likerName: string }
  | { type: 'follow'; followerName: string };

为什么叫"可辨识"?

因为每个类型都有一个共同的属性(这里是 type),它的值可以用来"辨识"具体是哪种类型。

类比一下:

可辨识联合就像快递包裹上的"品类标签"——看到"电子产品"你就知道要轻拿轻放,看到"生鲜食品"你就知道要冷藏。标签决定了你该怎么处理它。

可辨识联合实战

typescript
加载中...

常见错误

常见错误

1. 联合类型只能访问共有属性

type StringOrNumber = string | number;

function process(value: StringOrNumber) {
  // value.length; // 错误!number 没有 length
  value.toString(); // 正确,string 和 number 都有 toString
}

2. 交叉类型属性冲突

type A = { name: string };
type B = { name: number };

type C = A & B;
// C 的 name 类型是 string & number,即 never——不可能的类型

3. 忘记处理所有联合类型分支

type Shape = { kind: 'circle'; radius: number }
           | { kind: 'rectangle'; width: number; height: number };

function getArea(shape: Shape): number {
  if (shape.kind === 'circle') {
    return Math.PI * shape.radius ** 2;
  }
  // 忘记处理 rectangle?TypeScript 会提醒你!
  // 如果不处理,返回值类型可能不对
  return shape.width * shape.height;
}

4. typeof 只能检查基本类型

typeof [] === 'object'; // true,但不够精确
Array.isArray([]); // true,用这个检查数组

动手实践

现在轮到你了!让我们用高级类型来完善博客系统。

练习1:文章类型系统

定义一个可辨识联合类型来表示博客文章的不同格式:

  • 纯文本文章:有 content 字段
  • 图片文章:有 imageUrl 和 caption 字段
  • 视频文章:有 videoUrl 和 duration 字段

练习2:搜索函数

实现一个搜索函数,参数可以是:

  • 字符串:按标题搜索
  • 数字:按ID搜索
  • 对象:按多个条件搜索

练习3:通知处理

扩展通知系统,添加新的通知类型(如"文章被推荐"),并确保处理了所有类型。

本章总结

恭喜你!你已经掌握了TypeScript的高级类型系统。

本章要点:

| 类型 | 符号 | 含义 | 比喻 | |-----|------|------|------| | 联合类型 | | | 或 | 米饭或面条 | | 交叉类型 | & | 且 | 成绩好且社团活跃 | | 类型守卫 | typeof/instanceof/in | 检查类型 | 安检出示证件 | | 可辨识联合 | type属性 | 通过标签区分 | 快递品类标签 |

对比一下:

| 学习前 | 学习后 | |-------|-------| | 类型定义死板 | 类型可以灵活组合 | | 处理不同类型靠猜 | 类型守卫安全收窄 | | switch语句没有类型保障 | 可辨识联合确保穷尽处理 |

下一章预告:

在下一章中,我们将学习异步编程——Promise和async/await。你的博客需要从服务器获取数据、保存文章,这些都需要异步操作。

就像学会了开车的基本操作,现在要上高速公路了!

交互式练习

1 / 1

可辨识联合练习:博客通知系统

实现一个通知处理函数,正确处理不同类型的通知

typescript
Loading...

章节测验

问题 1 / 3得分: 0 / 3

联合类型使用什么符号?

A
&
B
|
C
&&
D
||