上一篇介绍了 TypeScript 中常用的工具类型:
- Pick:挑选字段
- Omit:排除字段
- Partial:字段变可选
- Required:字段变必填
这些工具类型解决的问题是:
如何基于已有类型快速生成新的类型。
但是它们背后还有一个更重要的思想:
TypeScript 的类型本身也可以进行计算和组合。
这篇继续学习 TypeScript 中几个更底层的类型工具:
keyofExcludeRecordReturnType
并理解 Omit 为什么可以通过其他类型组合实现。
1. keyof:获取对象类型的所有字段名
假设有一个用户类型:
typescript
interface User {
id: number;
name: string;
age: number;
email: string;
}
这个类型包含:
bash
User
id
name
age
email
如果想获取 User 的所有字段名:
ini
type UserKeys = keyof User;
得到:
ini
type UserKeys = "id" | "name" | "age" | "email";
也就是说:
sql
keyof User
会把对象类型转换成:
所有 key 组成的联合类型。
可以理解为:
bash
User
{
id
name
age
email
}
|
↓
"id" | "name" | "age" | "email"
2. Exclude:从联合类型中删除成员
Exclude 用于处理联合类型。
例如:
ini
type All = "id" | "name" | "age" | "email";
现在不想要 email:
ini
type AfterExclude = Exclude<All, "email">;
结果:
arduino
"id" | "name" | "age"
可以理解为:
bash
原联合类型:
id
name
age
email
删除:
email
剩余:
id
name
age
注意:
Exclude 处理的是:
类型集合
而不是对象。
例如:
ini
type Keys = "a" | "b" | "c";
type Result = Exclude<Keys, "b">;
结果:
arduino
"a" | "c"
3. Omit 的实现原理
之前学习:
ini
type UserSafe = Omit<User, "email">;
意思:
从 User 中删除 email。
结果:
css
{
id:number;
name:string;
age:number;
}
那么 TypeScript 是怎么实现 Omit 的?
实际上:
r
Omit<T, K>
等价于:
r
Pick<T, Exclude<keyof T, K>>
看起来复杂,但是拆开就很简单。
第一步:keyof 获取所有字段
sql
keyof User
得到:
arduino
"id" | "name" | "age" | "email"
第二步:Exclude 删除不要的字段
arduino
Exclude<
"id" | "name" | "age" | "email",
"email"
>
得到:
arduino
"id" | "name" | "age"
第三步:Pick 重新选择字段
sql
Pick<User, "id" | "name" | "age">
得到:
css
{
id:number;
name:string;
age:number;
}
所以:
sql
Omit<User,"email">
内部逻辑:
sql
User
↓ keyof
"id" | "name" | "age" | "email"
↓ Exclude 删除 email
"id" | "name" | "age"
↓ Pick
新的 User 类型
4. Record:创建键值映射类型
前面的工具类型都是:
基于已有类型修改。
而 Record 是:
根据规则创建一个新的对象类型。
语法:
swift
Record<key类型, value类型>
例如:
ini
type Dict = Record<string, number>;
表示:
创建一个对象:
- key 必须是 string
- value 必须是 number
所以:
css
const obj: Dict = {
a:1,
b:2
}
正确。
但是:
css
const obj: Dict = {
a:"hello"
}
错误。
因为 value 应该是 number。
实际应用:错误码映射
HTTP 状态码:
- 400 参数错误
- 401 未登录
- 403 权限不足
- 404 资源不存在
- 500 服务器错误
可以定义:
ini
type ErrorMsgMap = Record<number, string>;
然后:
css
const errorMessage: ErrorMsgMap = {
400:"请求参数错误",
401:"请重新登录",
403:"权限不足",
404:"资源不存在",
500:"服务器内部错误"
};
获取错误信息:
lua
function getErrMsg(code:number):string {
return errorMessage[code] ?? "未知错误";
}
5. ReturnType:获取函数返回值类型
TypeScript 可以自动推断函数返回值:
csharp
function fn(){
return {
x:1,
y:2
};
}
TypeScript 知道:
css
{
x:number;
y:number;
}
如果想直接获取这个返回类型:
ini
type FnReturn = ReturnType<typeof fn>;
拆开理解:
typeof fn
获取函数类型:
css
() => {
x:number;
y:number;
}
ReturnType
获取返回值:
css
{
x:number;
y:number;
}
6. 这些工具类型之间的关系
它们并不是独立存在的。
很多复杂类型都是通过组合产生的:
markdown
对象类型 User
|
|
keyof
↓
字段联合类型
|
|
Exclude
↓
删除字段
|
|
Pick
↓
新的对象类型
7. 常见工具类型总结
| 工具类型 | 作用 | 示例 |
|---|---|---|
| keyof | 获取对象所有 key | keyof User |
| Pick | 挑选字段 | Pick<User,'id'> |
| Omit | 删除字段 | Omit<User,'email'> |
| Exclude | 删除联合类型成员 | Exclude<A,B> |
| Partial | 全部字段可选 | Partial<User> |
| Required | 全部字段必填 | Required<User> |
| Record | 创建键值映射 | Record<string,number> |
| ReturnType | 获取函数返回类型 | ReturnType<typeof fn> |
总结
TypeScript 高级类型的核心不是记住每一个工具类型,而是理解:
类型也可以进行计算。
通过:
keyof获取字段Exclude处理联合类型Pick重新组合对象类型Record创建映射结构ReturnType提取函数类型
我们可以在大型项目中避免重复定义接口,让类型随着业务变化自动同步。
这也是 React + TypeScript 项目中大量使用工具类型的原因:
一个核心类型,通过类型组合,生成不同页面、接口、表单需要的数据结构。