typescript高级特性-泛型

262 阅读2分钟

为什么要使用泛型

使用泛型最大的用处在于,它可以用来定义程序中各个成员互相之间的类型约束。这些成员可以是类的实例,可以是类的方法,函数参数或者函数的返回值等等

示例

下面是一个stack类

class Stack {
    private data = []
    push = (item) => this.data.push(item)
    pop = () => this.data.pop()
}
const stk = new Stack()

stk.push('1') //1

stk.push(1) //2

假如,由多个开发者,分别写了1和2处的代码,隐患就在这时悄悄地产生了。比如另外一位开发者写了这样的代码

stk.pop().toUpperCase() //运行时错误

上面的代码会抛出一个运行时错误。这些问题产生的根本原因,是我们没有对Stack类做任何的类型约束,那么下面就加一个类型约束看看

class Stack {
    private data = []
    push = (item: string) => this.data.push(item)
    pop = (): string => this.data.pop()
}
const stk = new Stack()
stk.push(1) //编译时错误

我们将data中的数据类型严格限制为字符串类型,这样就不会出现第一个代码示例中的错误。但是如果我还需要一个栈来存放数值类型的值应该怎么办呢?重新实现一个NumberStack类么?

class NumberStack {
    private data = []
    push = (item: number) => this.data.push(item)
    pop = (): number => this.data.pop()
}
const numStk = new NumberStack()
numStk.push(1) 

大家可以看出来这样做是很多余的,我需要保存几种数据类型的值,就需要去实现几个Stack,代码变得又臭又长。这时候泛型就可以出场了

class Stack<T> {
    private data = []
    push = (item: T) => this.data.push(item)
    pop = (): T => this.data.pop()
}

const numStk = new Stack<number>()
const stringStk = new Stack<string>()
numStk.push(1)
stringStk.push('1')

可以注意一下写法,我们在定义Stack类时,传入了一个泛型参数T,之后在向data添加数据的时候规定了只能够传入T数据类型。这时候类型之间的制约关系就形成了,我们只需要在实例化Stack时给T传参即可。

再看一个例子

function pop<T> (arr: T[]): T {
    return arr.pop()
}

上面的代码中,类型约束产生在函数调用参数和函数返回值之间。比如给一个保存了数值类型的数组作为参数,则函数必须返回数值类型的值

什么时候需要泛型

你想要描述一个什么样的约束?如果不能较快地回答出这个问题,那这个泛型很可能是无用的。看以下代码

declare function func<T> (item: string): T

上面地函数声明中,T只用在了一处,它没有与其他任何数据产生关联,自然也就没有产生任何约束,这就是一个无用的泛型。

希望对大家有所帮助,有不对的地方欢迎大佬们指正。