什么是 TypeScript 泛型

相关问题

  • TypeScript 泛型的作用是什么

回答关键点

工具 使用时指定类型

TypeScript 泛型是一种工具。它能让开发者不在定义时指定类型,而在使用时指定类型。

知识点深入

1. 泛型类

类型参数在类名后面的尖括号中指定。泛型类可以具有泛型字段或方法。

1class HZFEMember<T, U> {
2  private id!: T;
3  private name!: U;
4
5  setMember(id: T, name: U): void {
6    this.id = id;
7    this.name = name;
8  }
9
10  show(): void {
11    console.log(`ID: ${this.id}, Name: ${this.name}`);
12  }
13}
14
15const member1 = new HZFEMember<number, string>();
16member1.setMember(1, "QingZhen");
17member1.show(); // ID: 1, Name: QingZhen
18
19const member2 = new HZFEMember<string, string>();
20member2.setMember("02", "Aki");
21member2.show(); // ID: 02, Name: Aki

2. 泛型接口

1interface HZFEMember<T, U> {
2  id: T;
3  name: U;
4}
5
6const member1: HZFEMember<number, string> = {
7  id: 1,
8  name: "QingZhen",
9};
10console.log(`ID: ${member1.id}, Name: ${member1.name}`); // ID: 1, Name: QingZhen
11
12const member2: HZFEMember<string, string> = {
13  id: "02",
14  name: "Aki",
15};
16console.log(`ID: ${member2.id}, Name: ${member2.name}`); // ID: 02, Name: Aki

函数类型的泛型接口

1interface ShowHZFEMember<T, U> {
2  (id: T, name: U): void;
3}
4
5const showHZFEMember: ShowHZFEMember<number, string> = function (id, name) {
6  console.log(`ID: ${id}, Name: ${name}`);
7};
8showHZFEMember(1, "QingZhen"); // ID: 1, Name: QingZhen
9
10const showHZFEMember2: ShowHZFEMember<string, string> = function (id, name) {
11  console.log(`ID: ${id}, Name: ${name}`);
12};
13showHZFEMember2("02", "Aki"); // ID: 02, Name: Aki

3. 泛型约束

在下面的例子中访问 member 的 id 属性时,因为编译器并不能证明 member 中有 id 属性,所以会报错。

1function getHZFEMember<T>(member: T): T {
2  console.log(`ID: ${member.id}`); // Property 'id' does not exist on type 'T'.
3  return member;
4}

如果我们想要限制函数只能处理带有 id 属性的类型,就需要列出对于 T 的约束要求。我们可以定义一个接口来描述约束条件,创建一个包含  id 属性的接口,使用这个接口和 extends 关键字来实现约束。

1interface Member {
2  id: number;
3}
4
5function getHZFEMember<T extends Member>(member: T): T {
6  console.log(`ID: ${member.id}`);
7  return member;
8}
9
10getHZFEMember("QingZhen"); // Argument of type 'string' is not assignable to parameter of type 'Member'.
11getHZFEMember({ id: 1, name: "QingZhen" }); // ID: 1

4. 内置的工具类型

TypeScript 提供了一些内置的工具类型,本质上也是通过范型来实现的。下面,我们通过几种常用的类型来看看它们是怎么实现的。

4.1 Partial

通过将 Type 中的所有属性都设置为可选来构造一个新的类型。

1interface Member {
2  id: number;
3  name: string;
4  age: number;
5}
6const hzfer: Member = {
7  id: 1,
8  name: "Qingzhen",
9}; // Property 'age' is missing in type '{ id: number; name: string; }' but required in type 'Member'.
10
11type HZFEMember = Partial<Member>;
12const hzfer2: HZFEMember = {
13  id: 1,
14  name: "Qingzhen",
15}; // No errors

源码:

1type Partial<T> = {
2  [P in keyof T]?: T[P];
3};

4.2 Required

通过将 Type 中的所有属性都设置为必选来构造一个新的类型。和 Partial 相反。

1interface Member {
2  id: number;
3  name: string;
4  age?: number;
5}
6const hzfer: Member = {
7  id: 1,
8  name: "Qingzhen",
9}; // No errors
10
11type HZFEMember = Required<Member>;
12const hzfer2: HZFEMember = {
13  id: 1,
14  name: "Qingzhen",
15}; // Property 'age' is missing in type '{ id: number; name: string; }' but required in type 'Required<Member>'

源码:

1type Required<T> = {
2  [P in keyof T]-?: T[P];
3};

4.3 Exclude<UnionType, ExcludedMembers>

从联合类型 UnionType 中排除 ExcludedMembers 中的所有联合成员来构造一个新的类型。

1type HZFEMemberProps = Exclude<"id" | "name" | "age", "age">;
2
3const hzferProp: HZFEMemberProps = "age"; // Type '"age"' is not assignable to type 'HZFEMemberProps'.

源码:

1type Exclude<T, U> = T extends U ? never : T;

4.4 Pick<Type, Keys>

从一个已有的类型 Type 中选择一组属性 Keys 来构造一个新的类型。

1interface Member {
2  id: number;
3  name: string;
4  age: number;
5}
6
7type HZFEMember = Pick<Member, "id" | "name">;
8
9const hzfer: HZFEMember = {
10  id: 1,
11  name: "QingZhen",
12  age: 18, // Object literal may only specify known properties, and 'age' does not exist in type 'HZFEMember'.
13};

源码:

1type Pick<T, K extends keyof T> = {
2  [P in K]: T[P];
3};

4.5 Omit<Type, Keys>

从一个已有的类型 Type 中移除一组属性 Keys 来构造一个新的类型。

1interface Member {
2  id: number;
3  name: string;
4  age: number;
5}
6
7type HZFEMember = Omit<Member, "id" | "age">;
8
9const hzfer: HZFEMember = {
10  id: 1, // Object literal may only specify known properties, and 'id' does not exist in type 'HZFEMember'.
11  name: "QingZhen",
12};

源码:

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

4.6 ReturnType

构造一个由函数的返回值的类型 Type 组成的类型。

1interface GetHZFEMember {
2  (id: number): {
3    id: number;
4    name: string;
5    age: number;
6  };
7}
8
9type HZFEMember = ReturnType<GetHZFEMember>; // type HZFEMember = { id: number; name: string; age: number; };

源码:

1type ReturnType<T extends (...args: any) => any> = T extends (
2  ...args: any
3) => infer R
4  ? R
5  : any;

4.7 infer

infer 表示在 extends 条件语句中待推断的类型变量。

上文的 ReturnType 源码中的 infer R 就代表了待推断的函数的返回值类型。

借助这个能力,我们可以通过 infer 来实现 tuple 转 union:

1// 如果泛型参数 T 满足约束条件 Array<infer I>,那么就返回这个类型变量
2type TypeOfArrayItem<T> = T extends Array<infer I> ? I : never;
3
4type MyTuple = [string, number];
5
6type MyUnion = TypeOfArrayItem<MyTuple>; // string | number

参考资料

  1. Generics