Hello Typescript(08)-- 泛型

294 阅读3分钟

背景:实现一个打印函数

function log(value: string): string {
  console.log(value);
  return value;
}
  • 需求+1:函数也要可以接受字符串数组

    这里我们可能想到了用函数重载。

    function log(value: string): string;
    function log(value: string[]): string[];
    function log(value: any): any {
      console.log(value);
      return value;
    }
    
  • 需求+2:这个函数会返回任何传入它的值,且类型保持一致。 我们可以用 any 类型,实现传入任何类型。但会丢失部分信息(类型之间的约束关系),忽略了 输入参数的类型和返回值的类型必须是一致的

    function log(value: any) {
      console.log(value);
      return value;
    }
    

    此时,我们用 类型变量,它是一种特殊的变量,只用于表示类型而不是值。

    function log<T>(value: T): T {
      console.log(value);
      return value;
    }
    

    变量 T 捕获传入参数的类型,之后我们就可以使用这个 T 类型,再将 T 作为返回值类型。 此时,这个版本的 log 函数叫做 泛型

什么是泛型

不预先确定的数据类型,具体类型在使用时才可以确定。

  • 两种调用方式

    • 指明类型

      log<string[]>(["a", "b", "c"]);
      
    • 类型推断

      log(["a", "b", "c"]);
      
  • 泛型类型

    我们不仅可以用泛型定义函数,还可以定义函数类型。 可以将泛型类型看成只定义类型的传参,帮助理解。

    function log<T>(value: T): T {
      console.log(value);
      return value;
    }
    
    type MyLog = <T>(value: T) => T;
    let myLog: MyLog = log;
    
    // 还可以使用不同的泛型参数名,只要数量和使用方式能对应上。
    type ULog = <U>(value: U) => U;
    
    // 还可以使用带有调用签名的对象字面量来定义泛型函数:
    type OLog = { <T>(value: T): T };
    
  • 泛型接口

    interface MyLog {
      // 泛型仅仅约束函数
      <T>(value: T): T;
    }
    
    // 泛型约束了整个接口,实现时必须指定类型,或在接口定义是指定默认类型
    interface MyLog<T = string> {
      (value: T): T;
    }
    
    let ilog: ILog<number> = log;
    
  • 泛型类

    泛型类指的是实例部分的类型,所以类的 静态属性 不能使用这个泛型类型。

    class Gen<T> {
      constructor() {}
      run(value: T): T {
        console.log(value);
        return value;
      }
    }
    let gen1 = new Gen<number>();
    gen1.run(1);
    gen1.run("1"); // error!
    let gen2 = new Gen(); // 默认 any
    gen2.run(1);
    gen2.run("1");
    
  • 泛型约束

    我们定义一个接口来描述约束条件。 使用这个接口和 extends 关键字来实现约束:

    interface Length {
      length: number;
    }
    function log<T extends Length>(value: T) {
      console.log(value.length);
      return value;
    }
    
    log("1");
    log([1, 2, 3]);
    log({ length: 1 });
    log(2); // error!
    

泛型有什么好处呢?

  • 函数和类可以轻松支持多种类型,增强程序的扩展性。
  • 不必写多条函数重载,冗长的联合类型声明,增加代码可读性。
  • 灵活控制类型之间的约束。

TypeScript 基础系列