Skip to main content

# 什么是 TypeScript 泛型

## 相关问题​

- TypeScript 泛型的作用是什么

## 回答关键点​

`工具` `使用时指定类型`

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

## 知识点深入​

### 1. 泛型类​

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

    class HZFEMember<T, U> {
      private id!: T;
      private name!: U;

      setMember(id: T, name: U): void {
        this.id = id;
        this.name = name;
      }

      show(): void {
        console.log(`ID: ${this.id}, Name: ${this.name}`);
      }
    }

    const member1 = new HZFEMember<number, string>();
    member1.setMember(1, "QingZhen");
    member1.show(); // ID: 1, Name: QingZhen

    const member2 = new HZFEMember<string, string>();
    member2.setMember("02", "Aki");
    member2.show(); // ID: 02, Name: Aki

### 2. 泛型接口​

    interface HZFEMember<T, U> {
      id: T;
      name: U;
    }

    const member1: HZFEMember<number, string> = {
      id: 1,
      name: "QingZhen",
    };
    console.log(`ID: ${member1.id}, Name: ${member1.name}`); // ID: 1, Name: QingZhen

    const member2: HZFEMember<string, string> = {
      id: "02",
      name: "Aki",
    };
    console.log(`ID: ${member2.id}, Name: ${member2.name}`); // ID: 02, Name: Aki

函数类型的泛型接口

    interface ShowHZFEMember<T, U> {
      (id: T, name: U): void;
    }

    const showHZFEMember: ShowHZFEMember<number, string> = function (id, name) {
      console.log(`ID: ${id}, Name: ${name}`);
    };
    showHZFEMember(1, "QingZhen"); // ID: 1, Name: QingZhen

    const showHZFEMember2: ShowHZFEMember<string, string> = function (id, name) {
      console.log(`ID: ${id}, Name: ${name}`);
    };
    showHZFEMember2("02", "Aki"); // ID: 02, Name: Aki

### 3. 泛型约束​

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

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

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

    interface Member {
      id: number;
    }

    function getHZFEMember<T extends Member>(member: T): T {
      console.log(`ID: ${member.id}`);
      return member;
    }

    getHZFEMember("QingZhen"); // Argument of type 'string' is not assignable to parameter of type 'Member'.
    getHZFEMember({ id: 1, name: "QingZhen" }); // ID: 1

### 4. 内置的工具类型​

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

#### 4.1 Partial<Type>​

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

    interface Member {
      id: number;
      name: string;
      age: number;
    }
    const hzfer: Member = {
      id: 1,
      name: "Qingzhen",
    }; // Property 'age' is missing in type '{ id: number; name: string; }' but required in type 'Member'.

    type HZFEMember = Partial<Member>;
    const hzfer2: HZFEMember = {
      id: 1,
      name: "Qingzhen",
    }; // No errors

源码：

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

#### 4.2 Required<Type>​

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

    interface Member {
      id: number;
      name: string;
      age?: number;
    }
    const hzfer: Member = {
      id: 1,
      name: "Qingzhen",
    }; // No errors

    type HZFEMember = Required<Member>;
    const hzfer2: HZFEMember = {
      id: 1,
      name: "Qingzhen",
    }; // Property 'age' is missing in type '{ id: number; name: string; }' but required in type 'Required<Member>'

源码：

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

#### 4.3 Exclude<UnionType, ExcludedMembers>​

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

    type HZFEMemberProps = Exclude<"id" | "name" | "age", "age">;

    const hzferProp: HZFEMemberProps = "age"; // Type '"age"' is not assignable to type 'HZFEMemberProps'.

源码：

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

#### 4.4 Pick<Type, Keys>​

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

    interface Member {
      id: number;
      name: string;
      age: number;
    }

    type HZFEMember = Pick<Member, "id" | "name">;

    const hzfer: HZFEMember = {
      id: 1,
      name: "QingZhen",
      age: 18, // Object literal may only specify known properties, and 'age' does not exist in type 'HZFEMember'.
    };

源码：

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

#### 4.5 Omit<Type, Keys>​

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

    interface Member {
      id: number;
      name: string;
      age: number;
    }

    type HZFEMember = Omit<Member, "id" | "age">;

    const hzfer: HZFEMember = {
      id: 1, // Object literal may only specify known properties, and 'id' does not exist in type 'HZFEMember'.
      name: "QingZhen",
    };

源码：

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

#### 4.6 ReturnType<Type>​

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

    interface GetHZFEMember {
      (id: number): {
        id: number;
        name: string;
        age: number;
      };
    }

    type HZFEMember = ReturnType<GetHZFEMember>; // type HZFEMember = { id: number; name: string; age: number; };

源码：

    type ReturnType<T extends (...args: any) => any> = T extends (
      ...args: any
    ) => infer R
      ? R
      : any;

#### 4.7 infer​

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

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

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

    // 如果泛型参数 T 满足约束条件 Array<infer I>，那么就返回这个类型变量
    type TypeOfArrayItem<T> = T extends Array<infer I> ? I : never;

    type MyTuple = [string, number];

    type MyUnion = TypeOfArrayItem<MyTuple>; // string | number

## 参考资料​

1. Generics

Loading script...