TypeScript 高级类型进阶:keyof、Exclude、Record 与类型组合思想

上一篇介绍了 TypeScript 中常用的工具类型:

  • Pick:挑选字段
  • Omit:排除字段
  • Partial:字段变可选
  • Required:字段变必填

这些工具类型解决的问题是:

如何基于已有类型快速生成新的类型。

但是它们背后还有一个更重要的思想:

TypeScript 的类型本身也可以进行计算和组合。

这篇继续学习 TypeScript 中几个更底层的类型工具:

  • keyof
  • Exclude
  • Record
  • ReturnType

并理解 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 项目中大量使用工具类型的原因:

一个核心类型,通过类型组合,生成不同页面、接口、表单需要的数据结构。

相关推荐
凤山老林1 小时前
数据库读写分离与动态路由实战:Spring Boot + ShardingSphere-JDBC 生产配置
数据库·spring boot·后端·分库分表·sharding-jdbc
DS随心转插件1 小时前
Grok生成的html怎么导出——AI导出鸭:大模型结构化输出的“最后一公里”工程化解构
前端·人工智能·ai·html·豆包·deepseek·ai导出鸭
এ慕ོ冬℘゜1 小时前
使用 jQuery 动态渲染表格与状态切换
前端·javascript·jquery
Zane19942 小时前
调用了 async 函数却没执行?一文讲透协程、event loop 与 await
后端·python
Zane19942 小时前
HashMap 为什么要在长度16、容量必须是2的幂这些细节上较劲
java·后端
人间凡尔赛2 小时前
2026 前端必修:4 个让 CSS 脱胎换骨的现代新特性(附实战代码)
前端·css
用户2181697049302 小时前
Flutter (二十) 轮播图
前端
前端snow2 小时前
ai agent - RAG 汇总
前端
掘金者阿豪2 小时前
若依启动突然报 Redis MISCONF?一次从应用报错到磁盘爆满的完整排查记录
后端