typescript泛型的使用

  |  

Partial

1
2
3
4
5
6
/**
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};

keyof 指的是把我们一个对象里面的 键值对里的键【key】 给罗列取出来,并把它们联合起来形成一种联合类型

1
2
3
4
5
6
7
8
9
10
11
type QunYou = Person & QunYouAttribute;
/**
QunYou = {
name: string;
age: number;
isLsp: boolean;
sex: '男' | '女' | 0 | 1;
}
*/

type QunYouKeys = keyof QunYou; // "name" | "age" | "isLsp" | "sex"

in 又是什么

Partial 里面的 P 充当了另一个泛型。

in 在这里充当一个遍历的作用

keyof T 进行一个个遍历并且每个都单独拿出来生成新的 “键值对”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 内部就会变成这样
type Partial<T> = {
[P in keyof T]?: T[P];
};

type newQunyou = Partial<QunYou>;

// ↓
type Partial<QunYou> = {
[P in 'name' | 'age' | 'isLsp' | 'sex']?: QunYou[P];
};

// ↓
type Partial = {
name?: QunYou["name"];
age?: QunYou["age"];
isLsp?: QunYou["isLsp"];
sex?:QunYou["sex"];
};

// ↓
type Partial = {
name?: string;
age?: number;
isLsp?: boolean;
sex?: '男' | '女' | 0 | 1;
};

所以这个 Partial 就达到我们的效果 【通过泛型让目标类型中的所有属性变为可选】

Pick

1
2
3
4
5
6
/**
* From T, pick a set of properties whose keys are in the union K
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};

通过上面 Partial 讲解这个 Pick 相信大伙应该好理解很多了。

Pick 接收两个参数做泛型。

这里第二个泛型 K 后面跟着约束条件 K extends keyof T,

泛型 的 extends, 与后续 运算的 extends 需要稍微留心做个区分。 这次看 extends 在这里充当 类似于判断的 “约束” 角色

意思就是说 这个 K 必须 符合 keyof T 这个集合里面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type QunYou = Person & QunYouAttribute;

type QunYouOnlyLsp = Pick<QunYou, 'name' | 'isLsp'>;
// 内部就会变成这样
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};

type QunYouOnlyLsp = Pick<QunYou, "name" | "isLsp">;

// ↓
// "name" | "isLsp" 是否包含在 K的集合里面? 是的,符合要求
type Pick<QunYou, "name" | "isLsp"> = {
[P in 'name' | 'isLsp']: QunYou[P];
};

// ↓
type Pick = {
name: string;
isLsp: boolean;
};

Extract

1
2
3
4
5
6
7
/**
* Extract from T those types that are assignable to U
*/
type Extract<T, U> = T extends U ? T : never;

就是在一个对象中取出你想要的几个值
Extract<QunYou, 'name'>

Omit 和 Exclude

1
2
3
4
5
6
7
8
9
/**
* Exclude from T those types that are assignable to U
*/
type Exclude<T, U> = T extends U ? never : T;

/**
* Construct a type with the properties of T except for those in type K.
*/
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

这里是 运算的 extends 书接上面 的泛型 的 extends,在此处记得对比。

同样扮演 “判断” 的角色, 但是 在所谓 “TS 中的三目运算符” 里面,

更像是一层 “过滤” 的作用。

我们依旧以上面的例子作为介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
type QunYou = Person & QunYouAttribute;

type QunDaLao = Omit<QunYou, 'name'>;

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

// ↓
type QunDaLao = Pick<QunYou, Exclude<keyof T, K>>

// 主要观察Exclude
type ExcludeKeys = Exclude<keyof QunYou, "name">

// ↓ type Exclude<T, U> = T extends U ? never : T;
type Exclude<"name" | "age" | "isLsp" | "sex", "name"> = T extends U ? never : T;

// ↓ 继续观察, 它将这么去 “判断”
// 在 T extends U ? never : T; 里面
// 将 T 里面的联合类型逐个逐个 与 U 进行 约束判断

// T 里面的 "name" 与 U 的 "name" (类型)符合吗? 符合, 返回 never ,既 什么都没有。
// T 里面的 "age" 与 U 的 "name" (类型)符合吗? 不符合 返回 "age" 本身。
// T 里面的 "isLsp" 与 U 的 "name" (类型)符合吗? 不符合 返回 "isLsp" 本身。
// T 里面的 "sex" 与 U 的 "name" (类型)符合吗? 不符合 返回 "sex" 本身。

// 所以 我们得到的结果就是 被 "过滤"后的 联合类型
type ExcludeResult = "age" | "isLsp" | "sex";

// 那么回过头继续看 Omit
type QunDaLao = Pick<QunYou, Exclude<keyof T, K>>

// ↓
type QunDaLao = Pick<QunYou, "age" | "isLsp" | "sex">
// 嗯?是不是很熟悉了,跟上面一样

// 所以 Omit 他的结果就达到了剔除 "name"的效果
type QunDaLao = {
age: number;
isLsp: boolean;
sex: '男' | '女' | 0 | 1;
}

最后 再聊一个 infer 与 typeof 关键字

1
2
3
4
5
6
7
const qunYous: QunHaiXing[] = [];

type whatIsQunYou<T> = T extends Array<infer V> ? V : never;

const newQunYou: whatIsQunYou<typeof qunYous> = {
// ...
};

infer 的意思是待推导一个泛型,

在这里形容更像是一个 “标记”,

我在这里先把 V 给标记了,等下你们给我推出这个 V 的类型然后让我用!

注意看 typeof 的位置,他处在泛型的位置,

这将意味着 “Ts 的 typeof” 和 “Js 和 typeof” 不是一个东西。

在普通写运行代码的地方 typeof 是真的会运行并返回出一个变量的类型字符串。

但是写在类型地方的 typeof 仅起到一个静态类型的作用(既不会真的返回一个字符串出来)

那他在这里的作用是什么

还是同一个思想,只不过他在这里 typeof 返回的是 TS 里的类型

1
2
3
4
5
6
7
const qunYous: QunHaiXing[] = [];

const newQunYou: whatIsQunYou<typeof qunYous>;

// ↓
const newQunYou: whatIsQunYou<Array<QunHaiXing>>;
// 或者是 whatIsQunYou<QunHaiXing[]>

回过头继续看 whatIsQunYou 中 infer 的标记 是怎么标记法!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type whatIsQunYou<T> = T extends Array<infer V> ? V : never;

const newQunYou: whatIsQunYou<Array<QunHaiXing>>;

// ↓
type whatIsQunYou< Array<QunHaiXing> > = Array<QunHaiXing> extends Array<infer V> ? V : never;

// T 里面的 Array<QunHaiXing> 与 Array<infer V> 类型是否符合吗? 符合, 符合返回 V
// ...
// 等等! V 是什么!!!
// ..
// 我们看看位置进行“对比”
// 我们用简单的语言进行概括。
// 把尖括号的位置 一样的!就赋值给 V!
// Array<QunHaiXing> 与 Array<infer V> 对比下
// 很明显 QunHaiXing 这个类型符合 V 的位置 所以把他赋值给V

// 重新来!!

// T 里面的 Array<QunHaiXing> 与 Array<infer V> 类型是否符合吗? 符合, 符合返回 QunHaiXing

这就是 infer 具有推导作用的关键字。

备注

文章来源

使用

使用 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
* 冻结客户类型
*/
export enum BlockOrUnblockCustomerActionEnum {
/** 永久冻结 */
TEMPORARY_BLOCK = 'TEMPORARY_BLOCK',
/** 临时冻结 */
PERMANENT_BLOCK = 'PERMANENT_BLOCK',
/** 解冻 */
UNBLOCK = 'UNBLOCK',
}

/**
* 冻结客户理由
*/
export enum BlockOrUnblockCustomerReasonEnum {
CUSTOMER_REQUEST = 'CUSTOMER_REQUEST',
CLIENT_REQUEST = 'CLIENT_REQUEST',
DECEASED = 'DECEASED',
ACCOUNT_CLOSURE = 'ACCOUNT_CLOSURE',
SUSPICIOUS_ACTIVITY = 'SUSPICIOUS_ACTIVITY',
FRAUDULENT_ACTIVITY = 'FRAUDULENT_ACTIVITY',
POTENTIAL_SANCTION = 'POTENTIAL_SANCTION',
SANCTIONED_CUSTOMER = 'SANCTIONED_CUSTOMER',
BLACKLISTED_CUSTOMER = 'BLACKLISTED_CUSTOMER',
}

export type IBlockOrUnblockCustomerReason = {
[BlockOrUnblockCustomerActionEnum.PERMANENT_BLOCK]: BlockOrUnblockCustomerReasonEnum;
[BlockOrUnblockCustomerActionEnum.TEMPORARY_BLOCK]: Extract<
BlockOrUnblockCustomerReasonEnum,
| BlockOrUnblockCustomerReasonEnum.CUSTOMER_REQUEST
| BlockOrUnblockCustomerReasonEnum.CLIENT_REQUEST
| BlockOrUnblockCustomerReasonEnum.SUSPICIOUS_ACTIVITY
| BlockOrUnblockCustomerReasonEnum.POTENTIAL_SANCTION
>;
[BlockOrUnblockCustomerActionEnum.UNBLOCK]: Extract<
BlockOrUnblockCustomerReasonEnum,
BlockOrUnblockCustomerReasonEnum.CUSTOMER_REQUEST | BlockOrUnblockCustomerReasonEnum.CLIENT_REQUEST
>;
};

export type BlockOrUnblockCustomerInput<K extends BlockOrUnblockCustomerActionEnum, T extends IBlockOrUnblockCustomerReason> = {
// 冻结类型
action: K;
// 理由
reason: T[K];
// 自定义
comment?: string;
};




public async blockOrUnblockCustomer<K extends BlockOrUnblockCustomerActionEnum, T extends IBlockOrUnblockCustomerReason>(
customerHashId: string,
dataParams: BlockOrUnblockCustomerInput<K, T>,
) {}

使用 2

实现 a 和 b 的类型是动态的; has_more 是不变的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface Data {
has_more: boolean;
a: {
id: string;
}[];
b: {
id: string;
number: string;
}[];
}

type Partial2<K extends keyof Data> = {
[P in K]: Data[P];
};

const b: Partial2<"a" | "has_more"> = {
a: [
{
id: "1",
},
],
has_more: true,
};
文章目录
  1. 1. Partial
    1. 1.1. keyof 指的是把我们一个对象里面的 键值对里的键【key】 给罗列取出来,并把它们联合起来形成一种联合类型
  2. 2. Pick
  3. 3. Extract
  4. 4. Omit 和 Exclude
  5. 5. 备注
  6. 6. 使用
    1. 6.1. 使用 1
    2. 6.2. 使用 2