愿你坚持不懈,努力进步,进阶成自己的巨人

—— 2017.09, 写给3年后的自己

Typescript学习记录:泛型

泛型在可重用组件里是一个很重要的特性,typescript也支持泛型

一、简单的例子

我们想要设计一个函数,函数的作用是返回任何我们传入的东西,如:

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

这个函数能够接受任何number类型的数据,并返回。但是,我们期望的是,除了number类型的数据,其他类型的数据也可以接收,所以可能会想:

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

咋一看,好像没有什么问题,也是可以返回任何我们传入的东西,但是其实这是有问题的,因为我们其实期待的是原样返回任何我们输入的数据,包括类型在内,但是单看这个函数定义,我们并不能看出这一点,所以更好的做法是使用泛型,如:

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

如此一来,T就成为了一个类型变量,它能够帮助我们捕获用户传入的类型,之后出现T的地方,都会被替换成用户传入的类型。所以使用泛型我们可以:
1)明确给T传入一个值,如:let x = identity<number>(123)
2)使用自动类型推导,如:let x = identity('Hello'),此时T将被推导为string类型
使用泛型的时候,我们在函数体内必须正确的使用这个通用的类型,如以下情况下的使用就是不合理的:

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

正确的做法是,根据实际用途调整T,如:

function showLength<T>(arg: T[]): T[] {
    console.log(arg.length)
    return arg
}
// 或者
function showLength<T>(arg: Array<T>): Array<T> {
    console.log(arg.length)
    return arg
}


三、泛型类型

一个泛型函数的类型如:

<泛型变量名称>(参数1: 泛型变量, 参数2: 泛型变量, ...参数n: 泛型变量) => 泛型变量

实例:

let showLength: <T>(arg: T) => T

我们还可以以对象字面量的形式来定义泛型函数(这更像是接口),如:

let foo: {<T>(arg: T): void}
foo = function<T>(arg: T): void {
    console.log(arg)
}

其中,{<T>(arg: T): void}可以写成:

interface ISomeType {
    <T>(arg: T): void
}

从而可以改写成:

let foo: ISomeType = function<T>(arg: T): void {
    console.log(arg)
}

此外,接口中也可以使用泛型,这样子就锁定了代码里可以使用的类型,如:

interface ISomeType<T> {
    <T>(arg: T): T
}

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

let numberFn: ISomeType<number> = fn


四、泛型类

除了泛型接口,我们还可以创建泛型类(但是无法创建泛型枚举、泛型命名空间),泛型类使用<>包围泛型类型变量,如:

class Demo<T> {
    prop: T
    show: (arg: T) => T
}
let d = new Demo<number>()
d.prop = 123 // 允许
d.prop = 'Hello' // 报错

d.show = function(arg: number) => number {
    return arg
} // 允许
d.show = function(arg: string) => string {
    return arg
} // 报错

注意:类有两部分,分为静态部分实例部分,泛型类的类型指的是实例部分的类型,静态属性不能使用该泛型类型


五、泛型约束

泛型可以通过extends一个接口来实现泛型约束,写法如:<泛型变量 extends 接口>,例子:

interface IArray {
    length: number
}

function print<T extends IArray>(arr: T): void {
    for (let i=0; i<arr.length; ++i) {
        console.log(i)
    }
}

let arr = [1, 2, 3]
print<number>(arr) // 报错
print<number[]>(arr) // 允许
print(arr) // 自动类型推导,允许

可以在泛型里使用类类型,如使用泛型创建工厂函数,需要引用构造函数的类类型,有:

function create<T>(c: { new(): T }): T {
    return new c()
}