内置工具类型

2026-09-09 00:00    #typescript  

TypeScript 自带一批工具类型,它们都是用前面几篇讲的条件类型和映射类型实现的。这一篇是完整目录加组合示例。

完整目录

  1// 内置工具类型完整目录(行为均经实测验证)
  2
  3interface User {
  4  id: number;
  5  name: string;
  6  email: string;
  7  age: number;
  8}
  9
 10// ==================== 对象变换 ====================
 11type P = Partial<User>;                        // 全部可选
 12type R = Required<P>;                          // 全部必需
 13type RO = Readonly<User>;                      // 全部只读
 14type Picked = Pick<User, "id" | "name">;       // 只保留指定键
 15type Omitted = Omit<User, "email">;            // 排除指定键
 16type Rec = Record<"a" | "b", number>;          // { a: number; b: number }
 17
 18// ==================== 联合变换 ====================
 19type U = Exclude<"a" | "b" | "c", "a">;        // "b" | "c"
 20type I = Extract<"a" | "b" | "c", "a" | "b">;  // "a" | "b"
 21type NN = NonNullable<string | null | undefined>;   // string
 22
 23// ==================== 函数变换 ====================
 24type Fn = (this: { id: number }, a: string, b: number) => boolean;
 25
 26type Ret = ReturnType<Fn>;                     // boolean
 27type Params = Parameters<Fn>;                  // [string, number]
 28type ThisT = ThisParameterType<Fn>;            // { id: number }
 29type NoThis = OmitThisParameter<Fn>;           // (a: string, b: number) => boolean
 30
 31// 构造函数相关
 32class Point { constructor(public x: number, public y: number) {} }
 33type CtorParams = ConstructorParameters<typeof Point>;   // [number, number]
 34type Inst = InstanceType<typeof Point>;                  // Point
 35
 36// ==================== Promise ====================
 37type A1 = Awaited<Promise<string>>;                  // string
 38type A2 = Awaited<Promise<Promise<number>>>;         // number(递归)
 39type A3 = Awaited<string | Promise<number>>;         // string | number
 40
 41// ==================== 字符串 ====================
 42type S1 = Uppercase<"a">;      // "A"
 43type S2 = Lowercase<"A">;      // "a"
 44type S3 = Capitalize<"ab">;    // "Ab"
 45type S4 = Uncapitalize<"Ab">;  // "ab"
 46
 47// ==================== 组合出项目专用工具 ====================
 48
 49// 把某个键变成可选
 50type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
 51
 52// 把某个键变成必需
 53type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
 54
 55// 只改一个键的类型
 56type Override<T, K extends keyof T, V> = Omit<T, K> & { [P in K]: V };
 57
 58// 深度 Partial
 59type DeepPartial<T> = {
 60  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
 61};
 62
 63// 深度 Readonly
 64type DeepReadonly<T> = {
 65  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
 66};
 67
 68// 至少一个键必需(互斥或)
 69type AtLeastOne<T, K extends keyof T = keyof T> = Omit<T, K> &
 70  { [P in K]: Required<Pick<T, P>> & Partial<Record<Exclude<K, P>, never>> }[K];
 71
 72// 从联合里挑出函数的返回类型
 73type UnionReturn<T> = T extends (...args: never[]) => infer R ? R : never;
 74
 75// 把联合转成交叉
 76type UnionToIntersection<U> =
 77  (U extends unknown ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
 78
 79// 取出值为某类型的键
 80type KeysOfType<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T];
 81
 82// ==================== 验证 ====================
 83
 84const partial: P = { id: 1 };
 85const required: R = { id: 1, name: "a", email: "b", age: 1 };
 86const picked: Picked = { id: 1, name: "a" };
 87const omitted: Omitted = { id: 1, name: "a", age: 1 };
 88const rec: Rec = { a: 1, b: 2 };
 89const excl: U = "b";
 90const extr: I = "a";
 91const nonNull: NN = "s";
 92const ret: Ret = true;
 93const params: Params = ["s", 1];
 94const thisT: ThisT = { id: 1 };
 95const noThis: NoThis = (a: string, b: number) => a.length > b;
 96const ctorParams: CtorParams = [1, 2];
 97const inst: Inst = new Point(1, 2);
 98const a1: A1 = "s";
 99const a2: A2 = 1;
100const a3: A3 = "s";
101const s1: S1 = "A";
102const s2: S2 = "a";
103const s3: S3 = "Ab";
104const s4: S4 = "ab";
105
106const optEmail: PartialBy<User, "email"> = { id: 1, name: "a", age: 1 };
107const reqEmail: RequiredBy<P, "email"> = { id: 1, email: "b" };
108const strId: Override<User, "id", string> = { id: "1", name: "a", email: "b", age: 1 };
109const deep: DeepPartial<{ a: { b: { c: number } } }> = { a: { b: {} } };
110const deepRo: DeepReadonly<{ a: { b: number } }> = { a: { b: 1 } };
111const atLeast: AtLeastOne<{ a: number; b: string }> = { a: 1 };
112const unionRet: UnionReturn<() => string> = "s";
113const unionInter: UnionToIntersection<{ a: 1 } | { b: 2 }> = { a: 1, b: 2 };
114const keysOfType: KeysOfType<User, string> = "name";   // "name" | "email"
115
116console.log(partial, required, picked, omitted, rec, excl, extr, nonNull, ret, params);
117console.log(thisT, noThis, ctorParams, inst, a1, a2, a3, s1, s2, s3, s4);
118console.log(optEmail, reqEmail, strId, deep, deepRo, atLeast, unionRet, unionInter, keysOfType);
1{ id: 1 } [Function: noThis] [ 1, 2 ] Point { x: 1, y: 2 } s 1 s A a Ab ab
2{ id: 1, name: 'a', age: 1 } { id: 1, email: 'b' } { id: '1', name: 'a', email: 'b', age: 1 } { a: { b: {} } } { a: { b: 1 } } { a: 1 } s { a: 1, b: 2 } name

对象变换

工具类型作用实现
Partial<T>全部可选{ [K in keyof T]?: T[K] }
Required<T>全部必需{ [K in keyof T]-?: T[K] }
Readonly<T>全部只读{ readonly [K in keyof T]: T[K] }
Pick<T, K>保留指定键{ [P in K]: T[P] }
Omit<T, K>排除指定键Pick<T, Exclude<keyof T, K>>
Record<K, V>构造键值对象{ [P in K]: V }

联合变换

工具类型作用实现
Exclude<T, U>排除可赋给 U 的成员T extends U ? never : T
Extract<T, U>只保留可赋给 U 的成员T extends U ? T : never
NonNullable<T>排除 null/undefinedT extends null | undefined ? never : T

这三个是分布式条件类型的直接应用(见 条件类型与 infer)。

函数变换

工具类型作用
ReturnType<T>返回类型
Parameters<T>参数元组
ThisParameterType<T>this 类型
OmitThisParameter<T>去掉 this 参数
ConstructorParameters<T>构造函数参数元组
InstanceType<T>构造函数实例类型

ThisParameterType 和 OmitThisParameter 配合使用:

1type Fn = (this: { id: number }, a: string, b: number) => boolean;
2
3type ThisT = ThisParameterType<Fn>;     // { id: number }
4type NoThis = OmitThisParameter<Fn>;    // (a: string, b: number) => boolean
ReturnType 对重载函数取最后一个签名

实测确认:重载函数用 ReturnType 时,得到的是最后一个重载签名的返回类型,不是所有签名的联合。写类型时要注意这个行为。

Promise

工具类型作用
Awaited<T>递归拆开 Promise
1Awaited<Promise<string>>              // string
2Awaited<Promise<Promise<number>>>     // number(递归)
3Awaited<string | Promise<number>>     // string | number

字符串(TS 4.1+)

工具类型作用
Uppercase<S>转大写
Lowercase<S>转小写
Capitalize<S>首字母大写
Uncapitalize<S>首字母小写

组合出项目专用工具

内置的 12 个不够用时,用它们拼。

把某个键变成可选

1type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

思路:先用 Omit 删掉这个键,再用 Partial<Pick> 加回来(这次是可选的)。

把某个键变成必需

1type RequiredBy<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;

只改一个键的类型

1type Override<T, K extends keyof T, V> = Omit<T, K> & { [P in K]: V };

Omit & Pick 是"改一个键"的标准套路。

深度变换

1type DeepPartial<T> = {
2  [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
3};
4
5type DeepReadonly<T> = {
6  readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
7};

至少一个键必需

1type AtLeastOne<T, K extends keyof T = keyof T> = Omit<T, K> &
2  { [P in K]: Required<Pick<T, P>> & Partial<Record<Exclude<K, P>, never>> }[K];

取出值为某类型的键

1type KeysOfType<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T];

末尾的 [keyof T] 是索引访问,把映射类型的值联合起来——这是"从映射类型提取联合"的标准技巧。

联合转交叉

1type UnionToIntersection<U> =
2  (U extends unknown ? (x: U) => void : never) extends (x: infer I) => void ? I : never;

靠函数参数的逆变位置实现(见 条件类型与 infer)。

查看源码

内置工具类型定义在 lib.es5.d.ts 里。在 TS 7 中这个文件在平台包里:

1# macOS ARM
2node_modules/@typescript/typescript-darwin-arm64/lib/lib.es5.d.ts

搜索 type Partial、type Pick、type Exclude 就能找到。它们都很短(大多 1-3 行),是学习类型编程最好的材料。

学习路径
  1. 读 lib.es5.d.ts 里的 Partial、Pick、Exclude、ReturnType
  2. 自己实现一遍,不看源码
  3. 做 Type Challenges 的 Easy
  4. 读真实库的类型定义(zod 的 z.infer、Prisma 的 GetPayload)

相关