Skip to main content

工具泛型

学习工具泛型的底层源码和实现原理,是通往高级前端(熟练掌握“类型体操”)的必经之路。掌握以下核心操作符和内置泛型的实现,可以应对绝大多数 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

  1. 用来扩展已有的类型
interface IAnimal = {
name: string;
}
interface ICat extends IAnimal {
action: string;
}

// 等价于
type ICat = IAnimal & {
action: string;
}
  1. 对类型进行条件限定,比如判断两种类型是否相等:
type IsEqualType<A, B> = A extends B ? (B extends A ? true : false) : false;
  1. 对于 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;

高级面试手写题:深度遍历

原生内置的 PartialReadonly 只会处理对象的第一层属性(浅层)。面试中经常要求手写深度版本的泛型(运用递归思想)。

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];
};