1 minute read

什么是泛型?

我们在编写程序时,经常遇到两个模块的功能非常相似,只是一个是处理 int 数据,另一个是处理 string 数据,或者其他自定义的数据类型,但我们没有办法,只能分别写多个方法处理每个数据类型,因为方法的参数类型不同。有没有一种办法,在方法中传入通用的数据类型,这样不就可以合并代码了吗?泛型的出现就是专门解决这个问题的。

组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。

当不使用泛型的时候

function identity(arg: number): number {
    return arg
}
function identity1(arg: string): string {
    return arg
}
function identity(arg: any): any {
    return arg
}

这样的两个函数的结构是一样的只是因为参数不一样,才导致需要写两个不同的函数,也许会想到用 any 来实现这样的效果,但是如果使用any的话,我们就无法确定值的类型是什么了,会丢失一些信息:传入的类型与返回的类型应该是相同的。

所以就有了下面这种写法

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

我们使用一个一个类型变量T 来接受变量的类型 ,如果参数是 number 类型的数据,则 T 的值就是 number。上面的这种形式的函数就叫做 泛型,它可以适用于多个类型。

泛型的使用

第一种,传入所有的参数,包含类型参数,下面的方法中使用<>明确的指定了Tstring类型的

let output = identity < string > "hello world"

第二种,使用类型推断 编译器会自动根据我们传入的参数来确定 T 的类型 (推荐使用)

let output = identity("hello world")

这样的使用方式就没必要使用尖括号(<>)来手动传入具体的类型,编译器会根据我们传入的值的类型来设置 T 的值

function identity<T>(arg: T): T {
    console.log(arg.length) // 类型“T”上不存在属性“length”
    return arg
}

因为类型变量代表的是任意类型,所以可以是 number 或者 Array 等,而数字是没有.length属性的。

要想实现上面的,可以如下方式来写

function identity<T>(arg: Array<T>): Array<T> {
    console.log(arg.length) // Array has a .length, so no more error
    return arg
}

identity 接收类型参数T和参数arg,它是个元素类型是T的数组,并返回元素类型是T的数组。 如果我们传入数字数组,将返回一个数字数组,因为此时 T的的类型为number。 这可以让我们把泛型变量 T 当做类型的一部分使用,而不是整个类型,增加了灵活性。

泛型类型

泛型类型的函数和非泛型函数的类型没什么不同,只是有一个类型参数在前面

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

let myIdentity: <T>(arg: T) => T = identity

// 使用不同的泛型参数名 ,只要在数量上和使用方式上能对应上就可以
function identity<T>(arg: T): T {
    return arg
}

let myIdentity: <U>(arg: U) => U = identity

// 使用带有调用签名的对象字面量来定义泛型函数
function identity<T>(arg: T): T {
    return arg
}

let myIdentity: { <T>(arg: T): T } = identity

用上面的的对象字面量拿出来作为接口

interface GenericIdentityFn {
    <T>(arg: T): T;
}

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

let myIdentity: GenericIdentityFn = identity

我们也可以把泛型参数当作整个接口的一个参数,因此可以如下修改代码

interface GenericIdentityFn<T> {
    (arg: T): T;
}

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

let myIdentity: GenericIdentityFn<number> = identity

泛型类

泛型类使用( <>)括起泛型类型,跟在类名后面

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

泛型约束

上面我们在使用 .length 的时候报错了,我们可以增加一个约束来实现这个。

interface Lengthwise {
    length: number;
}
function identity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length)
    return arg;
}

增加了约束后,我们调用的方式也得做修改

// 这样是不行的
identity(2) // Error, number doesn't have a .length property

需要传入带约束的值

identity({ length: 2, value: 3 })

Updated: