写在前面:今天这节课内容有点"杂",但每一样都特别实用。先是热身------用 Docker 三行命令把 MySQL 8.0 跑了起来(为后面的全栈项目准备的"数据库基础设施");然后进入正题,老师开始教 TS 高级工具类型:Pick、Omit、Partial、Record、Exclude、keyof、ReturnType。翻译成人话就是:怎么给类型做"瘦身手术" ------一个接口太大,怎么只挑需要的字段?怎么去掉敏感字段?怎么让所有字段都可选?最爽的是 Omit 的原理------原来它就是
Pick + Exclude + keyof三个工具拼出来的,我还亲手复刻了一遍。所有代码都是课堂真实代码,一个字符没改。
一、先热个身:Docker 三行命令把 MySQL 跑起来
老师今天的第一件事:本地装个 MySQL,以后全栈项目要用数据库。装 MySQL 的方式不是去官网下载安装包,而是用 Docker 拉镜像、起容器:
bash
# 1. 拉镜像(版本不一样)
docker pull mysql
# 2. 起容器:后台运行,名字 mysql-demo,宿主机 3307 端口映射容器内 3306,root 密码 123456
docker run -d --name mysql-demo -p 3307:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0
这一长串命令,拆开看每个参数都很好懂:
| 参数 | 含义 |
|---|---|
-d |
后台运行(detached) |
--name mysql-demo |
给容器起个名字 |
-p 3307:3306 |
宿主机 3307 端口 → 容器内 3306 端口 |
-e MYSQL_ROOT_PASSWORD=123456 |
环境变量:root 密码 |
mysql:8.0 |
指定镜像版本 |
注意端口映射的小心机:容器内 MySQL 默认监听 3306,但我们映射到宿主机的 3307------这样不会和本机已有的 3306 端口冲突。
然后进容器、连数据库:
bash
# 进入容器的 linux 终端
docker exec -it mysql-demo /bin/bash
# 容器里连接 mysql
mysql -uroot -p123456
docker exec = 钻进正在运行的容器里执行命令;-it 是交互式终端。进了容器就是一个 Linux 终端,mysql 客户端直接连上。今天的"基础设施",就绪。
二、TS 高级类型:给类型做"瘦身手术"
热身结束,正片开始。老师给出了一个很常见的场景:
ts
interface User {
id: number;
name: string;
age: number;
email: string;
}
一个 User 接口有 4 个字段,但不同的场景需要的字段不一样。老师写在代码里的注释是原话:
"有什么特性 一个类型挑选一些你需要的字段,形成新的类型?负责项目,区分度。大型项目类型消费比较多"
翻译一下:大型项目里,同一个类型会被很多地方"消费",但每个地方只需要其中一部分字段。所以,要给类型做"瘦身手术"。
2.1 Pick:挑选需要的字段
ts
// 有什么特性 一个类型挑选一些你需要的字段,形成新的类型?
type UserPreview = Pick<User, 'id' | 'name'>
const u: UserPreview = {
id: 1,
name: 'qxh',
}
Pick<T, K>:从类型 T 里挑选出 K 指定的字段,组成新类型。列表页只需要 id 和 name,就挑这两个------多了不给,页面更清爽。
2.2 Omit:去掉不想要的字段
ts
// Omit 去掉部分字段
type UserSafe = Omit<User, 'email'>
const safeUser : UserSafe = {
id: 1,
name: 'qxh',
age: 11,
}
Omit<T, K>:从 T 里去掉 K 字段。比如 email 是敏感字段,对外返回时直接剔除------瘦身的同时还保护隐私,一举两得。
2.3 Partial:所有字段全部变可选
ts
// 所有字段全部变成可选
type PartialUser = Partial<User>;
// patch 修改
const patchUser:PartialUser = {
name: '嫣嫣',
age: 18
}
const emptyObj: PartialUser = {};
Partial<T>:把所有字段变成可选的。看注释------"patch 修改"。做 PATCH 接口(部分更新)的时候,用户可能只改 name,不改别的。全量接口用 User,部分更新用 Partial<User>,字段传不传都行,完美适配。
2.4 Record:键值对字典
ts
// json key:value Record<键类型, 值类型>
type Dict = Record<string, number>;
const obj: Dict = { a: 1, b: 2 };
type ErrorMsgMap = Record<number, string>;
// http status code
// 1XX 执行中
// 2XX 成功
// 3XX 要跳转
// 4XX 用户错误
// 5XX 服务器端错误
const errorMessage: ErrorMsgMap = {
400: '请求参数错误',
401: '未登录,请重新登录',
403: '权限不足,禁止反问',
404: '资源找不到',
500: '服务器内部错误',
}
function getErrMsg(code: number) {
return errorMessage[code] ?? '未知错误';
}
Record<K, V>:造一个键是 K 类型、值是 V 类型的"字典"。最经典的用法就是 HTTP 状态码映射表------1XX 执行中、2XX 成功、3XX 要跳转、4XX 用户错误、5XX 服务器端错误。查表用 ?? '未知错误' 兜底,查不到就返回默认值,永远不会返回 undefined。
2.5 ReturnType:函数返回类型
ts
function fn() { return {x:1, y:2}};
type fnReturn = ReturnType<typeof fn>;
ReturnType<typeof fn>:直接把函数的返回类型"抽"出来用。函数返回 {x, y},fnReturn 就是 {x: number, y: number}。函数改返回结构,类型自动跟着变,不用手写第二遍。
2.6 重头戏:Omit 的原理(keyof + Exclude + Pick)
readme 里老师留了一道思考题:
"Omit<T, K> 等价于 Pick<T, Exclude<keyof T, K>> 怎么理解?"
我照着 2.ts 亲手复刻了一遍 Omit:
ts
type UserKeys = keyof User; // "id" | "name" | "age" | "email"
type KeepKeys = Exclude<UserKeys, "email">; // "id" | "name" | "age"
type MyOmitUser = Pick<User, KeepKeys>; // { id: number; name: string; age: number }
分三步走,readme 里讲得明明白白:
keyof T:拿到类型所有键的联合类型("id" | "name" | "age" | "email")Exclude:把要剔除的K从联合类型里删掉,剩下需要保留的键Pick:用剩下的键从原类型里挑选字段 → 就实现了 Omit 的效果
这就是 TS 内部 Omit 的等价实现------读源码不如自己拼一遍,拼完就懂了。readme 还留了个小知识点:
"Exclude 处理联合类型,Omit 处理对象接口"
Exclude 是"联合类型"的减法,Omit 是"对象接口"的减法:一个管字符串联合,一个管接口字段,各司其职。
三、总结:TS 工具类型全家桶
| 工具类型 | 作用 | 一句话 |
|---|---|---|
Pick<T, K> |
挑选字段 | 列表页只要 id 和 name |
Omit<T, K> |
去掉字段 | email 不要了 |
Partial<T> |
全部可选 | PATCH 部分更新 |
Record<K, V> |
键值对字典 | HTTP 状态码映射表 |
Exclude<A, B> |
联合类型减法 | 去掉 email |
keyof T |
取所有键 | "id" | "name" | ... |
ReturnType<fn> |
函数返回类型 | 抽出来直接用 |
工具类型就是给类型做手术的器械:想瘦哪里瘦哪里,想保护哪个字段就保护哪个字段。
写在最后
今天这节课"含金量"很足:Docker 三行命令让 MySQL 跑了起来(以后全栈项目有数据库了),TS 工具类型让我第一次体会到"类型也是可以编程的"------Pick、Omit、Partial、Record 这些工具类型,就像给类型做手术的器械。最惊喜的是亲手复刻出 Omit 的那一刻:原来复杂的高级类型,就是简单工具的排列组合,Pick + Exclude + keyof 拼一拼,Omit 就自己长出来了。
下次面试官问你:"TS 的 Omit 是怎么实现的?Pick 和 Omit 有什么区别?"
你可以淡定地说:
"Omit<T, K> 等价于 Pick<T, Exclude<keyof T, K>>:先用 keyof T 拿到类型所有键的联合类型,再用 Exclude 把要剔除的 K 从联合类型里删掉,最后用 Pick 把剩下的键从原类型中挑选出来。Exclude 处理的是联合类型,Omit 处理的是对象接口。实际使用中,Pick 是从类型里挑选需要的字段(比如列表页只要 id 和 name),Omit 是去掉不需要的字段(比如对外返回时剔除 email),Partial 是把所有字段变成可选(适合 PATCH 部分更新接口),Record 用来构造键值对字典(比如 HTTP 状态码和错误消息的映射表),ReturnType 可以直接抽出函数的返回类型。"
然后看着面试官满意的表情,心里默念:这波,又稳了。
本文所有代码示例均来自课堂学习资料,真实可运行。