TypeScript基础(二)泛型的应用

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情

泛型函数

泛型: 是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

定义泛型函数

正常声明一个变量的时候,像是下面这样。

const a: string = "kong";
复制代码

泛型,是在使用的时候才会指定类型(一般用大写的T做变量,Type的简写,用其他的也都没问题),如下:

  function fn1<T>(value: T): T {
    return value;
  }
  fn1<string>("kong");
  fn1(1);
复制代码

上面的函数fn1添加了类型变量 T,T能够捕获到用户传入的参数类型,然后再根据T做函数返回值的类型。

调用的时候可以<>手动设定类型,也可以不设置让ts自动进行类型推导。

  • 默认类型

声明函数的时候,可以传给T一个默认类型。调用函数的时候,不传递类型就是默认类型。

  function fn1<T = number>(value: T): T {
    return value;
  }
复制代码
  • 多个类型参数

定义泛型的时候,可以一次性定义多个类型参数

  function swap<T, U>(arr: [T, U]): [U, T] {
    return [arr[1], arr[0]]
  }
  swap([100, "haha"]); // ["haha", 100]
复制代码

类型约束

在函数内部使用泛型的变量的时候,由于还不知道它属于哪种类型,所以不能随意操作变量的属性和方法。

如下: value.length 会出现错误信息 类型T上不存在属性length

  function fn2<T>(value: T){
    value.length // 错误:类型T上不存在属性length
  }
复制代码

这时候就可以使用 类型约束,关键字 extends

interface HasLength {
  length: number
}

  function fn3<T extends HasLength>(value: T) {
    value.length
  }
复制代码

上述代码,让泛型T继承自Haslength接口,表明传入的参数要遵守 HasLength接口的规则,也就是必须要含有length属性。

也可以直接这样继承:

  function fn3<T extends {length: number}>(value: T) {
    value.length
  }
复制代码

此时,如果传入不包含length属性的参数,就会在编译阶段提示错误:

  fn3([1,2,3]); // 正确
  fn3("KONG"); // 正确
  fn3(100); // 错误
复制代码

提示错误: 类型number的参数不能赋值给类型HasLength的参数。

使用泛型编写一个好的函数要注意的点

  1. 可能的情况下,使用类型参数本身,而不是对其进行约束
  2. 尽可能少的使用类型参数
  3. 如果一个类型的参数只出现在一个地方,请重新考虑是否真的需要它

泛型接口

定义一个标准的接口,应该像下面这样:

  interface Person {
    name: string,
    age: number
  }
  const p1: Person = {
    name: "tom",
    age: 18
  }
复制代码
  • 简单使用

上面接口里面的类型是固定写死的,如果需要由用户传入接口的类型,那就需要使用泛型来定义接口:

  interface Person2<T1, T2> {
    name: T1,
    age: T2,
  }
  const p2: Person2<string, number> = {
    name: "jack",
    age: 18
  }
复制代码

上面的例子是在使用的时候传入了类型,也可以在定义接口的时候给泛型默认值:

  interface Person2<T1 = string, T2 = number> {
    name: T1,
    age: T2,
  }
复制代码
  • 使用泛型接口的方式定义函数需要符合的形状
  interface CreateArr {
    <T>(length: number, value: T): Array<T>
  }

  let createArrFn: CreateArr;
  createArrFn = function <T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
      result[i] = value;
    }
    return result;
  }
复制代码

上面的接口,使用泛型定义了一个函数。然后创建了createArrFn方法,实现了这个泛型接口的函数。可以传入两个参数lengthvalue,其中length为数组的长度所以是number类型,value类型为泛型,根据调用方法时传入参数的类型决定。最终这个方法会返回一个长度为length的数组。

泛型类

  • 在类名后面定义泛型T,类里面使用
  class GenAny<T> {
    value: T;
    add: (x: T, y: T) => T;
  }
复制代码
  const myGen = new GenAny<number>();
  myGen.value = 100;
  myGen.add = (x, y) => x + y;
  // 调用
  myGen.add(3, 4);
复制代码
  • 应用: 定义一个 Person
  class Person<T1, T2> {
    name: T1;
    age: T2;
    sex: T1;
    constructor(name: T1, age: T2, sex: T1){
      this.name = name;
      this.age = age;
      this.sex = sex;
    }
  }
复制代码

上面的Person类包含了泛型T1 T2name age sex属性。

实例化:

  1. 自动类型推导,可以不传入参数的类型:
const p1 = new Person("tom", 18, "boy");
复制代码

自动推断出T1string类型,T2number类型。此时如果第三个参数sex传入非string类型就会提示错误,以为已经把T1自动推导为string类型了。

  1. 不用类型推导就要在new实例化的时候传入类型
 const p2 = new Person<string, string>("jack", "18", "boy");
复制代码
  1. 使用类来约束对象并传递类型
 const p3: Person<string, number> = new Person("jack", 18, "boy");
复制代码

这时候可以发现上一节中用Array类来声明数组的写法和使用泛型类约束变量的写法是很相似的:

 const arr: Array<number> = [2, 4, 6];
复制代码

猜你喜欢

转载自juejin.im/post/7109363837807116319