← [ BACK_TO_NOTES ]

TypeScript Utility Types

把 Partial、Required、Pick、Omit、Record、Readonly、ReturnType、Parameters、Awaited 等内置 Utility Types 按裁剪、映射与推导三类理解,并说明它们与 keyof、映射类型和条件类型的关系。

#synthesis #evergreen #dev #typescript

[!info] related notes

TypeScript Utility Types

核心定位

Utility Types 不是一组需要逐个死背的魔法类型。

更稳定的理解是:

它们是 TypeScript 已经替你写好的常见类型变换。

底层主要建立在:

  • 泛型;
  • keyof;
  • 映射类型;
  • 条件类型;
  • infer;

之上。

三类最常用能力

裁剪 / 修改对象属性

  • Partial<T>:全部属性可选;
  • Required<T>:全部属性必选;
  • Pick<T, K>:只保留指定键;
  • Omit<T, K>:排除指定键;
  • Readonly<T>:全部属性只读。
interface User {
  id: number
  name: string
  email: string
}

type UpdateUser = Partial<Pick<User, "name" | "email">>

建立键到值的映射

Record<K, V> 用一组 key 构造对象映射:

type Role = "admin" | "editor" | "viewer"

type RoleLabels = Record<Role, string>

从既有类型推导

  • ReturnType<T>:函数返回值;
  • Parameters<T>:函数参数元组;
  • Awaited<T>:展开 Promise / thenable 的结果类型。
type ApiResult = ReturnType<typeof fetchUser>
type Args = Parameters<typeof fetchUser>
type Value = Awaited<Promise<string>>

为什么它们比重复写 DTO 更稳

当新类型本质上是已有契约的变换时:

重新手写第二份结构
→ 两份定义可能漂移

从已有类型推导
→ 变化沿类型关系传播

因此 Utility Types 的真正价值是表达类型之间的关系,而不只是减少几行代码。

不要过度组合

如果一个类型需要套很多层:

Partial<Omit<Pick<...>>>

并且业务语义已经看不清,应考虑:

  • 给中间类型命名;
  • 直接声明领域契约;
  • 检查是否把内部实体模型泄漏到 API/UI 边界。

“能推导”不等于“应该推导”。

手写理解

type MyPartial<T> = {
  [K in keyof T]?: T[K]
}

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P]
}

这里真正要理解的是:

  • keyof T 得到 key 集合;
  • in 遍历 key;
  • 映射类型重新构造对象属性。

学习检查

不看正文回答:

  1. Utility Types 的本质为什么是“类型变换”而不是一组独立语法?
  2. Pick 与 Omit 分别从什么方向裁剪对象类型?
  3. Record<K, V> 与普通随手写对象类型相比,什么时候更能表达约束?
  4. ReturnType / Parameters 为什么能减少重复定义?
  5. Partial<Pick<T, K>> 表达了怎样的类型关系?
  6. 为什么过度嵌套 Utility Types 反而会降低领域可读性?
  7. 手写一个最小 Readonly<T> 或 Pick<T, K>,解释每一段语法。
创建于 2026/9/30 更新于 2026/9/30
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本