工具泛型
学习工具泛型的底层源码和实现原理,是通往高级前端(熟练掌握“类型体操”)的必经之路。掌握以下核心操作符和内置泛型的实现,可以应对绝大多数 TS 相关的面试手写题。
核心操作符 (类型编程的基石)
typeof
获取变量的类型
keyof
keyof T,获取类型 T 的所有键组成的联合类型,keyof 也被称为 索引类型查询操作符
interface IPerson {
id: string;
age: number;
}
type IPersonKeys = keyof IPerson; // 'id' | 'age'
type IPersonKeys = IPerson[keyof IPerson]; // string | number
[]
索引访问操作符,可以进行索引访问
interface T {
K: string;
}
type TypeK = T[K]; // string
in
可以对联合类型进行遍历。通过 [K in Keys] 可以实现映射类型,从旧类型中创建新类型的一种方式
type Index = 'a' | 'b' | 'c'
type FromIndex = { [K in Index]?: number }
const index_1: FromIndex = { b: 1, c: 2 }
const index_2: FromIndex = { b: 1, d: 3 } // 报错,不能添加d属性
extends
- 用来扩展已有的类型
interface IAnimal = {
name: string;
}
interface ICat extends IAnimal {
action: string;
}
// 等价于
type ICat = IAnimal & {
action: string;
}
- 对类型进行条件限定,比如判断两种类型是否相等:
type IsEqualType<A, B> = A extends B ? (B extends A ? true : false) : false;
- 对于 T extends U ? X : Y 来说,还存在一个特性,当 T 是一个联合类型时,会进行条件分发。这一点对于理解后面的工具泛型很关键
type Demo = string | number;
type IsString<T> = T extends string ? "yes" : "no";
type IsDemoString = IsString<Demo>; // 'yes' | 'no';
实际上,extends 的转换类似于下面这一步:
(string extends string ? 'yes' : 'no') | (number extends string ? 'yes' : 'no')
infer
在有条件类型的 extends 子语句中,允许出现 infer 声明,它会引入一个待推断的类型变量,相当于在类型空间中声明了一个变量来承载推导出的类型。
// 提取函数的返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
// 提取 Promise 的返回值类型(高频面试题:实现 Awaited)
type MyAwaited<T> = T extends Promise<infer U> ? MyAwaited<U> : T;
内置工具泛型及其实现原理
Record
将 K 中所有的属性的值转化为 T 类型
type Record<K extends keyof any, T> = { [P in K]: T };
Pick
从 T 中取出一系列 K 的值
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
Exclude
从 T 中排除 U
type Exclude<T, U> = T extends U ? never : T;
Extract
从 T 中提取 U
type Extract<T, U> = T extends U ? T : never;
Omit
相当于 Pick + Exclude, 实现忽略对象某些属性功能,Pick+Exclude
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
Partial
将 T 中的属性都变为可选属性
type Partial<T> = { [P in keyof T]?: T[P] };
Required
将 T 中的属性都变为必选属性(-? 代表移除可选修饰符)
type Required<T> = { [P in keyof T]-?: T[P] };
Readonly
将 T 中的属性都变为只读属性
type Readonly<T> = { readonly [P in keyof T]: T[P] };
Mutable (非内置,但常考)
将 T 中的属性都变为可写属性(-readonly 代表移除只读修饰符)
type Mutable<T> = { -readonly [P in keyof T]: T[P] };
ReturnType
获取函数的返回值的类型
type ReturnType<T extends (...args: any[]) => any> = T extends (
...args: any[]
) => infer R
? R
: never;
Parameters
获取函数的参数的类型,返回一个元组类型
type Parameters<T extends (...args: any[]) => any> = T extends (
...args: infer P
) => any
? P
: never;
高级面试手写题:深度遍历
原生内置的 Partial 和 Readonly 只会处理对象的第一层属性(浅层)。面试中经常要求手写深度版本的泛型(运用递归思想)。
DeepPartial
深度可选。需要判断属性值是否为对象,如果是则递归调用 DeepPartial。
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
DeepReadonly
深度只读。
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};