泛型编程(下)

本文介绍了一些 TS 内置工具类型的用法及实现

Readonly & Mutable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Person = { id: number; name: string; age: number };

type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
type X1 = MyReadonly<Person>;
// {
// readonly id: number;
// readonly name: string;
// readonly age: number;
// }

type MyMutable<T> = {
-readonly [K in keyof T]: T[K];
};
type X9 = MyMutable<Readonly<Person>>;
// {
// id: number;
// name: string;
// age: number;
// }

注意:

  • Mutable 并不是 TS 内置的函数
  • -readonly [] 表示去掉属性前面的 Readonly

Partial & Required

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Person = { id: number; name: string; age: number };

type MyPartial<T> = {
[K in keyof T]?: T[K];
};
type X2 = MyPartial<Person>;
// {
// id?: number;
// name?: string;
// age?: number;
// }

type MyRequired<T> = {
[K in keyof T]-?: T[K];
};
type X3 = MyRequired<Person>;
// {
// id: number;
// name: string;
// age: number;
// }

注意 -?,表示去掉属性的可选,变为必选

Exclude & Extract

1
2
3
4
5
type MyExclude<A, B> = A extends B ? never : A;
type X5 = Exclude<1 | 2 | 3, 1 | 2>; // 3

type MyExtract<A, B> = A extends B ? A : never;
type X6 = MyExtract<1 | 2 | 3, 2 | 4>; // 2

按照之前讲的乘法分配律,有如下理解过程:

1
2
3
4
5
6
7
8
type X5 = 1 | 2 | 3 extends 1 | 2 ? never : A

type X5 =
| 1 extends 1 | 2 ? never : 1 // never
| 2 extends 1 | 2 ? never : 2 // never
| 3 extends 1 | 2 ? never : 3 // 3

type X5 = never | never | 3 // 3

X6 的理解过程同 X5,这里不赘述

Omit & Pick

1
2
3
4
5
6
7
8
9
10
11
12
13
type Person = { id: number; name: string; age: number };

type MyOmit<T, Key extends keyof T> = Pick<T, Exclude<keyof T, Key>>;
// 或者
type MyOmit<T, Key extends keyof T> = {
[K2 in keyof T as (K2 extends Key ? never : K2)]: T[K2];
};
type X7 = MyOmit<Person, 'name' | 'age'>; // { id: number }

type MyPick<T, Key extends keyof T> = {
[K2 in Key]: T[K2];
};
type X8 = MyPick<Person, 'name' | 'age'>; // { name: string; age: number }
  • Key extends keyof T 是一个泛型约束,约束所传的 key 必须包含在 keyof T 中,即必须是 'id' | 'name' | 'age' 的子集
  • MyOmit 的第二个写法中,集合 K2 extends Key ? never : K2 小于或等于集合 K2 in keyof T,依旧遵循之前讲的类型兼容,类型小的可赋值给类型大的,查看实现思路

Record

1
2
3
4
5
type MyRecord<Key extends string | number | symbol, Value> = {
[k in Key]: Value;
};

type X4 = MyRecord<string, string>; // { [x: string]: string }

ReturnType

1
2
3
4
5
6
7
8
9
10
11
type MyReturnType<T extends (...args: any[]) => unknown> =
T extends (...args: any[]) => infer U
? U
: never

const fn = (v: boolean) => {
if (v) return 1
else return 2
}

type Result = MyReturnType<typeof fn> // 应推导出 "1 | 2"

Awaited

1
2
3
4
5
6
7
type MyAwaited<T extends PromiseLike<any>> = T extends PromiseLike<infer U>
? U extends PromiseLike<any>
? MyAwaited<U>
: U
: never;

type Result = MyAwaited<Promise<string>> // string

PromiseLike 表示一个对象具有 then 方法,它可以像 Promise 一样使用链式调用。

PromiseLike 可以表示内置的 Promise 对象和第三方库或自定义实现的 thenable 对象,因此 PromiseLike 提供了更广泛的兼容性。

  1. 限制传入的类型必须是 PromiseLike
  2. 使用 infer 推导 PromiseLike 参数类型
  3. 如果推导类型仍为 PromiseLike,就循环调用 MyAwaited
  4. 否则直接返回推导类型

Parameters

1
2
3
4
5
6
7
8
type MyParameters<T extends (...args: any[]) => unknown> =
T extends (...args: infer P) => unknown
? P
: never;

const foo = (arg1: string, arg2: number): void => {}

type Result = MyParameters<typeof foo> // [arg1: string, arg2: number]
  1. 限制传入的类型必须是函数类型
  2. 使用 infer 推导函数的参数类型
  3. 由于 ...args 本身已经是元组类型,因此 infer P 最终推导出的,也是元组类型

其他

这里推荐一个用于练习 TS 类型体操的 repo:TypeScript 类型体操姿势合集

看看别人的体操:


(●'◡'●)ノ♥