跳至主要內容

TypeScript 工具类型

YihuiTypeScript大约 6 分钟

TypeScript 工具类型

工具类型的分类

  • **属性修饰工具类型。**对属性的修饰,包括对象属性和数组元素的可选/必选、只读/可写。
  • 结构工具类型。对既有类型的裁剪、拼接、转换等,比如使用对一个对象类型裁剪得到一个新的对象类型,将联合类型结构转换到交叉类型结构。
  • 集合工具类型。对集合(即联合类型)的处理,即交集、并集、差集、补集。
  • 模式匹配工具类型。基于 infer 的模式匹配,即对一个既有类型特定位置类型的提取,比如提取函数类型签名中的返回值类型。
  • 模板字符串工具类。模板字符串专属的工具类型,比如神奇地将一个对象类型中的所有属性名转换为大驼峰的形式。

属性修饰工具类型

这一部分的工具类型主要使用属性修饰映射类型索引类型相关(索引类型签名、索引类型访问、索引类型查询均有使用,因此这里直接用索引类型指代)。

Partial, Required, Readonly

Partial 与 Required 可以认为是一对工具类型,它们的功能是相反的,而在实现上,它们的唯一差异是在索引类型签名处的可选修饰符,Partial 是 ?,即标记属性为可选,而 Required 则是 -?,相当于在原本属性上如果有 ? 这个标记,则移除它。

type Partial<T> = {
    [P in keyof T]?: T[P];
    // [P in keyof T]+?: T[P];
};

type Required<T> = {
    [P in keyof T]-?: T[P];
};

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

结构工具类型

这一部分的工具类型主要使用条件类型以及映射类型索引类型

结构工具类型其实又可以分为两类,结构声明结构处理

Record 记录

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

其中,K extends keyof any 即为键的类型,这里使用 extends keyof any 标明,传入的 K 可以是单个类型,也可以是联合类型,而 T 即为属性的类型。

// 键名均为字符串,键值类型未知
type Record1 = Record<string, unknown>;
// 键名均为字符串,键值类型任意
type Record2 = Record<string, any>;
// 键名为字符串或数字,键值类型任意
type Record3 = Record<string | number, any>;

日常常用Record<string, unknown>Record<string, any>来代替Object

结构处理 Pick、Omit

Pick

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

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

Pick接受两个泛型参数,T 即是我们会进行结构处理的原类型(一般是对象类型),而 K 则被约束为 T 类型的键名联合类型。由于泛型约束是立即填充推导的,即你为第一个泛型参数传入 Foo 类型以后,K 的约束条件会立刻被填充。

interface Foo {
  name: string;
  age: number;
  job: JobUnionType;
}

type PickedFoo = Pick<Foo, "name" | "age">

等价于

type Pick<T> = {
    [P in "name" | "age"]: T[P];
};

联合类型的成员会被依次映射,并通过索引类型访问来获取到它们原本的类型。

Omit

而对于 Omit 类型,看名字其实能 get 到它就是 Pick 的反向实现:Pick 是保留这些传入的键,比如从一个庞大的结构中选择少数字段保留,需要的是这些少数字段,而 Omit 则是移除这些传入的键,也就是从一个庞大的结构中剔除少数字段,需要的是剩余的多数部分。

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

Omit 是基于 Pick 实现的,这也是 TypeScript 中成对工具类型的另一种实现方式。上面的 Partial 与 Required 使用类似的结构,在关键位置使用一个相反操作来实现反向,而这里的 Omit 类型则是基于 Pick 类型实现,也就是反向工具类型基于正向工具类型实现Exclude<keyof T, K> 其实就是 T 的键名联合类型中剔除了 K 的部分。

集合工具类型

集合概念:

  • 并集,两个集合的合并,合并时重复的元素只会保留一份(这也是联合类型的表现行为)。
  • 交集,两个集合的相交部分,即同时存在于这两个集合内的元素组成的集合。
  • 差集,对于 A、B 两个集合来说,A 相对于 B 的差集即为 A 中独有而 B 中不存在的元素 的组成的集合,或者说 A 中剔除了 B 中也存在的元素以后剩下的部分
  • 补集,补集是差集的特殊情况,此时集合 B 为集合 A 的子集,在这种情况下 A 相对于 B 的补集 + B = 完整的集合 A

Extract 交集与Exclude 差集

内置工具类型中提供了交集与差集的实现:

type Extract<T, U> = T extends U ? T : never;

type Exclude<T, U> = T extends U ? never : T;

这里的具体实现其实就是条件类型的分布式特性,即当 T、U 都是联合类型(视为一个集合)时,T 的成员会依次被拿出来进行 extends U ? T1 : T2 的计算,然后将最终的结果再合并成联合类型。

Extract 交集运行逻辑

type AExtractB = Extract<1 | 2 | 3, 1 | 2 | 4>; // 1 | 2

type _AExtractB =
  | (1 extends 1 | 2 | 4 ? 1 : never) // 1
  | (2 extends 1 | 2 | 4 ? 2 : never) // 2
  | (3 extends 1 | 2 | 4 ? 3 : never); // never

Exclude 差集运行逻辑

type SetA = 1 | 2 | 3 | 5;

type SetB = 0 | 1 | 2 | 4;

type AExcludeB = Exclude<SetA, SetB>; // 3 | 5
type BExcludeA = Exclude<SetB, SetA>; // 0 | 4

type _AExcludeB =
  | (1 extends 0 | 1 | 2 | 4 ? never : 1) // never
  | (2 extends 0 | 1 | 2 | 4 ? never : 2) // never
  | (3 extends 0 | 1 | 2 | 4 ? never : 3) // 3
  | (5 extends 0 | 1 | 2 | 4 ? never : 5); // 5

type _BExcludeA =
  | (0 extends 1 | 2 | 3 | 5 ? never : 0) // 0
  | (1 extends 1 | 2 | 3 | 5 ? never : 1) // never
  | (2 extends 1 | 2 | 3 | 5 ? never : 2) // never
  | (4 extends 1 | 2 | 3 | 5 ? never : 4); // 4

由差集和交集的实现,我们可以对其他集合关系进行实现(非内置工具类型):

// 并集
export type Concurrence<A, B> = A | B;

// 交集
export type Intersection<A, B> = A extends B ? A : never;

// 差集
export type Difference<A, B> = A extends B ? never : A;

// 补集
export type Complement<A, B extends A> = Difference<A, B>;

模式匹配工具类型

这一部分的工具类型主要使用条件类型infer 关键字

在条件类型一节中我们已经差不多了解了 infer 关键字的使用,而更严格地说 infer 其实代表了一种 模式匹配(pattern matching) 的思路,如正则表达式、Glob 中等都体现了这一概念。

https://jishuin.proginn.com/p/763bfbd7299f

https://juejin.cn/post/7045536402112512007