首页/12

12章:实用工具类型:类型系统的瑞士军刀

Partial、Pick、Omit、Record——从现有类型派生新类型

45分钟
4个学习目标

学习目标

  • 1掌握Partial和Required的使用场景
  • 2学会使用Pick和Omit选择或排除属性
  • 3理解Record和Readonly的应用
  • 4学会自定义工具类型

上一章回顾:装饰器让代码优雅了,但类型定义还是有点累

在上一章中,我们学会了用装饰器优雅地扩展类和方法的功能。日志、权限、缓存,一行装饰器就搞定。

但你可能遇到了新的烦恼:

博客有一个 User 类型:

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  avatar: string;
  bio: string;
  createdAt: Date;
}

然后你需要:

  • 更新用户资料时,只需要传要修改的字段(不需要全部)
  • 创建用户时,不需要 id(数据库自动生成)
  • 显示用户列表时,不需要 password(安全考虑)
  • 用户设置页面,需要一个键值对的映射

难道要为每种场景都定义一个新接口?太累了!

TypeScript的工具类型就是解决这个问题的。

类比一下:

工具类型就像"瑞士军刀"——一把刀可以变出剪刀、开瓶器、螺丝刀。你不需要带一堆工具,只需要一把瑞士军刀,根据需要变换形态。

Partial<T>:所有属性变可选

Partial<T> 把类型 T 的所有属性变成可选的。

为什么需要它?

更新用户资料时,你只想传要修改的字段:

// 没有 Partial——必须传所有字段
function updateUser(id: number, user: User): User {
  return { ...currentUser, ...user };
}
updateUser(1, { name: '新名字', email: 'new@email.com', ... }); // 还要传 password、avatar...

// 有 Partial——只传需要的
function updateUser(id: number, updates: Partial<User>): User {
  return { ...currentUser, ...updates };
}
updateUser(1, { name: '新名字' }); // 只传要改的!

类比一下:

Partial 就像"选填表格"——所有字段都是选填的,你填多少都行。没有 Partial 就像"必填表格"——每个空都必须填。

Partial 实战

typescript
加载中...

Pick<T, K> 和 Omit<T, K>:选择或排除属性

Pick 从类型中选择指定的属性,Omit 从类型中排除指定的属性。

为什么需要它们?

博客有不同的场景需要不同的字段:

| 场景 | 需要的字段 | 不需要的字段 | |-----|-----------|-------------| | 用户列表 | id, name, avatar | email, password | | 用户详情 | id, name, email, avatar, bio | password | | 创建用户 | name, email, password | id(自动生成)| | 更新资料 | name, email, avatar, bio | id, password |

用 Pick 和 Omit 可以从 User 类型派生出这些变体,而不用重复定义。

类比一下:

Pick 就像"挑选"——从一篮子水果里只拿苹果和香蕉。 Omit 就像"排除"——从一篮子里拿走所有苹果,剩下的都要。

Pick 和 Omit 实战

typescript
加载中...

Record<K, V>:创建键值对类型

Record<K, V> 创建一个键类型为 K、值类型为 V 的对象类型。

为什么需要它?

博客的分类系统需要一个映射:

// 没有 Record——类型不够精确
const categories: any = {
  tech: { name: '技术', count: 10 },
  life: { name: '生活', count: 5 },
};

// 有 Record——类型精确
type Category = { name: string; count: number };
const categories: Record<string, Category> = {
  tech: { name: '技术', count: 10 },
  life: { name: '生活', count: 5 },
};

类比一下:

Record 就像"字典"——每个词条(键)都有对应的释义(值),而且所有词条和释义都遵循统一的格式。

Record 和 Readonly 实战

typescript
加载中...

其他实用工具类型

TypeScript 还提供了更多工具类型,这里介绍几个常用的:

Exclude 和 Extract——操作联合类型

type T0 = Exclude<'a' | 'b' | 'c', 'a'>; // 'b' | 'c'
type T1 = Extract<'a' | 'b' | 'c', 'a' | 'f'>; // 'a'

NonNullable——排除 null 和 undefined

type T2 = NonNullable<string | null | undefined>; // string

ReturnType 和 Parameters——获取函数类型信息

function add(a: number, b: number) { return a + b; }
type Result = ReturnType<typeof add>; // number
type Params = Parameters<typeof add>; // [number, number]

自定义工具类型

typescript
加载中...

常见错误

常见错误

1. 混淆 Pick 和 Omit

// Pick——选择 id 和 name
type A = Pick<User, 'id' | 'name'>; // { id: number; name: string; }

// Omit——排除 id 和 name
type B = Omit<User, 'id' | 'name'>; // { email: string; password: string; ... }

Pick 是"只要这些",Omit 是"不要这些"。

2. Partial 不能用于嵌套对象

interface Config {
  api: { baseUrl: string; timeout: number; };
}

type PartialConfig = Partial<Config>;
// api 仍然是必需的!只是 api 本身变成了可选
// 不能 PartialConfig = { api: { baseUrl: 'xxx' } }
// 需要 DeepPartial

3. Record 的键类型必须是可赋值的

// 正确
type A = Record<string, number>; // { [key: string]: number }

// 错误——不能用对象作为键
type B = Record<{ id: number }, string>; // 报错!

4. Readonly 只是浅层只读

interface User {
  settings: { theme: string; };
}

const user: Readonly<User> = {
  settings: { theme: 'dark' },
};

// user = { ... }; // 错误
user.settings.theme = 'light'; // 这是可以的!settings 内部不是只读

动手实践

现在轮到你了!让我们用工具类型来优化博客的类型系统。

练习1:用户类型变体

基于 User 接口,创建以下类型:

  • UserPreview:只有 id、name、avatar
  • CreateUserInput:创建用户时的输入(没有 id)
  • UpdateUserInput:更新用户时的输入(所有字段可选,没有 id 和 password)

练习2:状态配置

用 Record 创建一个状态配置映射,确保每个状态都有对应的配置。

练习3:深度工具类型

实现一个 DeepReadonly 类型,让嵌套对象的所有属性都是只读的。

本章总结

恭喜你!你已经掌握了TypeScript的实用工具类型。

本章要点:

| 工具类型 | 作用 | 比喻 | |---------|------|------| | Partial<T> | 所有属性变可选 | 选填表格 | | Required<T> | 所有属性变必需 | 必填表格 | | Pick<T, K> | 选择指定属性 | 从篮子里挑苹果 | | Omit<T, K> | 排除指定属性 | 从篮子里拿走苹果 | | Record<K, V> | 创建键值对类型 | 字典 | | Readonly<T> | 所有属性变只读 | 只读文件 |

对比一下:

| 学习前 | 学习后 | |-------|-------| | 每种场景定义一个接口 | 从基础类型派生 | | 类型定义重复、难维护 | 类型复用、易维护 | | 不同接口可能不同步 | 修改基础类型,所有变体自动更新 |

课程回顾:

从第1章到现在,你已经学会了:

  1. TypeScript基础——类型注解、基础类型
  2. 接口和类型别名——定义数据结构
  3. 函数类型——让函数更安全
  4. 类——面向对象编程
  5. 泛型——写可复用的代码
  6. 模块系统——组织代码
  7. 项目完善——测试和部署
  8. 高级类型——灵活的类型组合
  9. 异步编程——处理异步操作
  10. 装饰器——优雅地扩展功能
  11. 工具类型——类型系统的瑞士军刀

下一步:

你现在已经掌握了TypeScript的核心知识。接下来可以:

  • 用 TypeScript 构建真实的博客项目
  • 学习 React + TypeScript
  • 探索更多高级特性(条件类型、映射类型)

TypeScript 的世界很大,但你已经打下了坚实的基础。继续加油!

交互式练习

1 / 1

工具类型练习:用户类型变体

使用工具类型从 User 接口派生出不同场景的类型

typescript
Loading...

章节测验

问题 1 / 4得分: 0 / 4

Partial<User> 的作用是什么?

A
所有属性变为必需
B
所有属性变为可选
C
选择特定属性
D
排除特定属性