前言

本文主要记录下 TypeScript 泛型知识点,日常学习总结篇。

一、概念

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

二、例子

首先来看一个简单的例子,体会一下泛型可解决的问题。

需求:定义一个 identity 函数,这个函数会返回任何传入它的值。如传入参数的类型是 number,函数返回类型为 number。

function identity(arg: number): number {
    return arg;
}

然后,需求变更,传入参数类型可以是 string,函数返回类型也是 string。

此时可以使用联合类型实现需求,代码如下。

function identity(arg: number | string): number | string {
    return arg;
}

然后需求再次变更,传入的参数类型可以支持更多类型,甚至任何类型时,如何实现呢?

此时可以使用 any 类型来定义函数,代码如下。

function identity(arg: any): any {
    return arg;
}

使用 any 类型会导致这个函数可以接收任何类型的 arg 参数,但是这样就丢失了一些信息:传入的类型与返回的类型应该是相同的。

例如传入一个 number 类型的参数,使用 string 类型来接收函数的返回值,此时 TS 并不会报错提示我们。代码如下。

const value: string = identity(89)

 这时候,泛型就派上用场了。代码如下。

function identity<T>(arg: T): T {
  return arg
}

这样就可以做到函数传入的类型与返回的类型一致,而且可以传入返回任何类型。 

如果类型不一致,TS 就会报错。

typescript get 和post方法 typescript function_元组

三、泛型基本使用

泛型的语法是 <> 里写类型参数,一般可以用 T 来表示。T 表示 Type,实际上 T 可以用任何有效的名称代替。除了 T 之外,常见的泛型变量还有 K、V 和 E 等。

  • K(key):表示对象中的键类型;
  • V(value):表示对象中的值类型;
  • E(element):表示元素类型。

泛型中的 T 被称为泛型变量,或者说一个类型占位符。就像传递参数一样,在使用的时候把指定的实际类型,链式传递给参数类型和返回值类型。

在使用的时候,可以有两种方式指定类型:

  • 定义具体类型;
  • TS 类型推断,自动推导出类型。
identity<string>('abc') // 定义具体类型

identity(123) // TS 类型推断,自动推导出类型

四、处理多个泛型参数

需求:定义一个函数,传入一个只有两项的元组类型数据,交换元组的值的顺序,返回这个元组。

实现代码如下:

function swap<T, U>(tuple: [T, U]): [U, T]{
    return [tuple[1], tuple[0]]
}

 传入一个参数,定义元组第 0 项为 string 类型,第 1 项为 number 类型。

const arr = swap<string, number>(['abc', 123])

得到的函数返回值,第 0 项为 number 类型,第 1 项为 string 类型。

第 0 项上全是 number 类型的方法,如下图所示。 

typescript get 和post方法 typescript function_泛型_02

第 1 项上全是 string 类型的方法,如下图所示。

typescript get 和post方法 typescript function_参数类型_03

五、泛型接口

定义接口时, 为接口中的属性或方法定义泛型类型,在使用接口时, 再指定具体的泛型类型。

interface ICreateObj<K, V> {
  name: K,
  age: V
}

const obj: ICreateObj<string, number> = {
  name: 'jenny',
  age: 18
}

六、泛型类

在类中使用泛型也很简单,只需要在类名后面,使用 <T, ...> 的语法定义任意多个类型变量。

class GenericFn<T> {
  defaultValue: T;
  add: (a: T, b: T) => T
}

// 传入 number 类型
const genericNumber = new GenericFn<number>()
genericNumber.defaultValue = 6
genericNumber.add = (x, y) => x + y
genericNumber.add(genericNumber.defaultValue, 8)  // 14

// 传入 string 类型
const genericString = new GenericFn<string>()
genericString.defaultValue = 'a'
genericString.add = (a, b) => a + b
genericString.add(genericString.defaultValue, 'c') // ac

七、默认参数

在 TypeScript 2.3 以后,我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推断出类型时,这个默认类型就会起作用。

泛型参数默认类型与普通函数默认值类似,对应的语法很简单,即 <T=Default Type>,对应的使用示例如下:

function identity<T = string>(arg: T): T {
  return arg
}

八、泛型约束

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

需求:实现一个函数,打印出传入参数的长度。

可以这样实现:

function getLength<T>(arg: T): T {
  console.log(arg.length)
  return arg
}

上例中,泛型 T 不一定包含属性 length,所以编译的时候 TS 会提示错误,类型 T 上不存在属性 length。如下图:

typescript get 和post方法 typescript function_参数类型_04

这时,就可以对泛型进行约束,只允许这个函数传入那些包含 length 属性的变量。

interface ILength {
  length: number
}

function getLength<T extends ILength>(arg: T): T {
  console.log(arg.length)
  return arg
}

上面代码中,最关键的就是 <T extends ILength>,让泛型 T 继承接口 ILength,也就是传入的参数类型必须包含 length 属性,这样就能约束泛型。

此时再次调用 getLength 函数的时候,传入的参数 arg 如果不包含 length 属性,那么在编译阶段 TS 就会报错。如下图所示。

typescript get 和post方法 typescript function_参数类型_05

此外,还可以使用 , 号来分隔多种约束类型,比如:<T extends Type1, Type2, Type3>