前言
本文主要记录下 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 就会报错。
三、泛型基本使用
泛型的语法是 <>
里写类型参数,一般可以用 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 类型的方法,如下图所示。
第 1 项上全是 string 类型的方法,如下图所示。
五、泛型接口
定义接口时, 为接口中的属性或方法定义泛型类型,在使用接口时, 再指定具体的泛型类型。
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。如下图:
这时,就可以对泛型进行约束,只允许这个函数传入那些包含 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 就会报错。如下图所示。
此外,还可以使用 ,
号来分隔多种约束类型,比如:<T extends Type1, Type2, Type3>
。