Skip to main content

TypeScript 中的泛型工具类

· 10 min read

归纳 TypeScript 中的泛型工具类型及其原理

前置知识

看本篇文章前请先理解下面几个前置知识:

工具类型作用
Partial<T>T 对象类型中所有属性变为可选属性
Required<T>T 对象类型中所有属性变为必选属性
Readonly<T>T 对象类型中所有属性变为只读属性
Pick<T, K>T 对象类型中挑选指定的几个属性返回新的类型,K 为字面量联合类型
Record<K, T>K 为字面量联合类型,将 K 作为新对象类型的属性,并且所有属性类型为 T
ReturnType<T>获取函数类型 T 的返回值类型
Parameters<T>获取函数类型 T 的所有参数类型,放在一个元组中,返回该元组类型
Exclude<T, U>让联合类型 T 集合减去 U 集合(差集)
Extract<T, U>取联合类型 TU 的交集
Omit<T, K>剔除对象类型 T 中的所有 K 集合属性,K 为字面量联合类型
NonNullable<T>过滤联合类型 T 中的 nullundefined 类型

Partial

Partial<T>

该工具类型的作用是 将一个对象类型中的所有属性变为可选属性

源码

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

使用 keyof 获取到 T 的所有键的联合类型形式,然后遍历它们的过程中加上 ? 变为可选属性,然后属性的类型使用 T[P] 来索引遍历到的属性的类型。

例子

interface Person {
name: string;
age: number;
}

// Ok
let p: Partial<Person> = {
name: "Yukee",
};

改进

Partial 有一定局限性,它只会给对象类型的第一层属性设置为可选属性。下面我们来实现一个 DeepPartial

type DeepPartial<T> = {
[P in keyof T]+?: T[P] extends object ? DeepPartial<T[P]> : T[P]
}

这里使用了 extends 的条件类型判断,如果 T[P] 也是一个对象类型 object,那么递归调用 DeepPartial 并且传入 T[P] 作为泛型参数,分析过程中一定要记住这里的 P 是字面量类型,来自 T 对象类型中的所有键,T[P] 则是键对应的类型。

Required

Required<T>

该工具类型的作用是 将一个对象类型中的所有属性变为必选属性

源码

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

P 遍历 T 中的所有属性,然后使用 -? 让属性变为可选属性,再使用 T[P] 索引属性的类型。

例子

interface Person {
name?: string;
age?: number;
}

type P = Required<Person>;
/*
equal:

type P = {
name: string;
age: number;
};
*/

改进

我们来实现一个 DeepRequired 版本。

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

Readonly

Readonly<T>

该工具类型的作用是 将一个对象类型中的所有属性变为只读属性

源码

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

原理很简单,就是每次遍历到 T 的属性时,在其前面加上 readonly 关键字即可。

Pick

Pick<T, K>

该工具类型的作用是 从一个对象类型中挑选部分属性出来返回一个新对象类型,这里的 K 必须传入字面量联合类型。

源码

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

要求 K 必须为 T 类型属性的字面量联合类型的子集,然后遍历 K 中的所有字面量类型,T[P] 即为该属性的类型。

例子

interface Animal {
type: string;
age: number;
gender: 0 | 1;
}

type Person = Pick<Animal, "age" | "gender">;
/*
equal:

type Person = {
age: number;
gender: 0 | 1;
};
*/

Record

Record<K, T>

该工具类型的作用是 将 K 中所有字面量类型作为对象类型的属性,然后将 T 作为每一项属性的类型,返回该对象类型

源码

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

要求 K 可以为任意的联合字面量类型,遍历这些字面量,让其作为新类型的属性,然后 T 为这些属性的类型。

例子

type PageType = "home" | "about" | "user";
interface Page {
title: string;
loading: boolean;
}

type P = Record<PageType, Page>;
/*
equal:

type P = {
about: Page,
home: Page,
user: Page
};
*/

Exclude

Exclude<T, U>

该工具类型的作用是 提取存在于T,但不存在于U的类型组成的联合类型。简单来说就是用 T 集合去减 U 集合,这里的集合指的是多种类型组成的联合类型。

源码

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

规定这里的 TU 都是联合类型,整个过程会遍历 T 中的所有子类型,如果该子类型存在于 U,则返回 never,否者返回 T,最终将返回的所有类型使用 | 联合起来,任何类型与 never 联合都属于本身。

例子

type P = Exclude<("a" | "b" | "c"), ("b" | "c")>; // type P = "a";
type F = Exclude<number | string | (() => {}), Function>; // type F = number | string;

Extract

Extract<T, U>

该工具类型的作用是 提取 T 和 U 中的交集,这里的 TU 必须是联合类型。

源码

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

逻辑和 Exclude 相反,整个过程会遍历 T 中的所有子类型,如果该子类型存在于 U,则返回 T,否者返回 never,最终将返回的所有类型使用 | 联合起来,任何类型与 never 联合都属于本身。

例子

type P = Extract<("a" | "b" | "c"), ("b" | "c")>; // type P = "b" | "c";

Omit

Omit<T, K>

该工具类型的作用是 从 T 类型的属性中剔除所有 K 字面量联合类型

源码

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

K 为任意的字面量联合类型,Exclude<keyof T, K> 表示由 T 的所有属性组成的集合减去 K 集合,然后再使用 Pick 挑选出 T 中剩余的属性。

例子

type ButtonProps = Omit<Partial<NativeButtonProps & AnchorButtonProps>, 'type'>;

从原生 button 和 a 标签属性中剔除 type 属性。

ReturnType

ReturnType<T>

该工具类型的作用是 获取一个函数类型的返回值类型,这里的 T 必须传入一个函数类型。

源码

type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;

这里涉及到了 infer 关键字,源码分析详见 TypeScript中的extends与infer,注意看清楚表达式左边和右边 = =。

例子

type Func = (value: number) => string;
const foo: ReturnType<Func> = "1"; // foo 为 string 类型

Parameters

Parameters<T>

该工具类型的作用是 获取函数类型 T 的所有参数类型,放在一个元组中,返回该元组类型

源码

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

这里涉及到了 infer 关键字,源码分析详见 TypeScript中的extends与infer,注意看清楚表达式左边和右边 = =。

例子

type Sum = (x: number, y: number) => number;
type ParamsType = Parameters<Sum>; // type ParamsType = [x: number, y: number]

NonNullable

NonNullable<T>

该工具类型的作用是 过滤联合类型中的 nullundefined

源码

type NonNullable<T> = T extends null | undefined ? never : T;

遍历 T 中的所有子类型,这些子类型如果是 null 或者 undefined 则返回 never,否者返回 T,最终将所有返回的类型进行联合。

Reference