学习目标
- 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 实战
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 实战
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 还提供了更多工具类型,这里介绍几个常用的:
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]
自定义工具类型
常见错误
常见错误
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章到现在,你已经学会了:
- TypeScript基础——类型注解、基础类型
- 接口和类型别名——定义数据结构
- 函数类型——让函数更安全
- 类——面向对象编程
- 泛型——写可复用的代码
- 模块系统——组织代码
- 项目完善——测试和部署
- 高级类型——灵活的类型组合
- 异步编程——处理异步操作
- 装饰器——优雅地扩展功能
- 工具类型——类型系统的瑞士军刀
下一步:
你现在已经掌握了TypeScript的核心知识。接下来可以:
- 用 TypeScript 构建真实的博客项目
- 学习 React + TypeScript
- 探索更多高级特性(条件类型、映射类型)
TypeScript 的世界很大,但你已经打下了坚实的基础。继续加油!
交互式练习
工具类型练习:用户类型变体
使用工具类型从 User 接口派生出不同场景的类型
章节测验
Partial<User> 的作用是什么?