归纳 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> | 取联合类型 T 和 U 的交集 |
Omit<T, K> | 剔除对象类型 T 中的所有 K 集合属性,K 为字面量联合类型 |
NonNullable<T> | 过滤联合类型 T 中的 null 或 undefined 类型 |
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;
规定这里的 T
和 U
都是联合类型,整个过程会遍历 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 中的交集,这里的 T
和 U
必须是联合类型。
源码
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>
该工具类型的作用是 过滤联合类型中的 null
和 undefined
。
源码
type NonNullable<T> = T extends null | undefined ? never : T;
遍历 T
中的所有子类型,这些子类型如果是 null
或者 undefined
则返回 never
,否者返回 T
,最终将所有返回的类型进行联合。