TS中的泛型是什么(下)

85 阅读2分钟

约束泛型

假设现在有这么一个函数,打印传入参数的长度,我们这么写:

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

因为不确定 T 是否有 length 属性,会报错:

约束泛型

可以和 interface 结合,来约束类型。

interface ILength {
    length: number
}

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

这其中的关键就是 <T extends ILength>,让这个泛型继承接口 ILength,这样就能约束泛型。

我们定义的变量一定要有 length 属性,比如下面的 str、arr 和 obj,才可以通过 TS 编译。

const str = printLength('lin') 
const arr = printLength([1,2,3]) 
const obj = printLength({ length: 10 })

只要你有 length 属性,都符合约束,那就不管你是 str,arr 还是obj,都没问题。

当然,我们定义一个不包含 length 属性的变量,比如数字,就会报错:

image.png

泛型的一些应用

使用泛型,可以在定义函数、接口或类的时候,不预先指定具体类型,而是在使用的时候再指定类型。

泛型约束类

定义一个栈,有入栈和出栈两个方法,如果想入栈和出栈的元素类型统一,就可以这么写:

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

在定义实例的时候写类型,比如,入栈和出栈都要是 number 类型,就这么写:

const s1 = new Stack<number>()

这样,入栈一个字符串就会报错:

image.png

这是非常灵活的,如果需求变了,入栈和出栈都要是 string 类型,在定义实例的时候改一下就好了:

const s1 = new Stack<string>()

image.png

特别注意的是,泛型无法约束类的静态成员

给 pop 方法定义 static 关键字,就报错了

静态成员不能引用类类型参数。(2302)

image.png

泛型约束接口

使用泛型,也可以对 interface 进行改造,让 interface 更灵活。

interface IKeyValue<T, U> { 
    key: T
    value: U
}
const k1:IKeyValue<number, string> = { 
    key: 18,
    value: 'lin'
}
const k2:IKeyValue<string, number> = { 
    key: 'lin', 
    value: 18
}

泛型定义数组

定义一个数组,我们之前是这么写的:

const arr: number[] = [1,2,3]
// 泛型
const arr: Array<number> = [1,2,3]

小结

泛型(Generics),从字面上理解,泛型就是一般的,广泛的。

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

泛型中的 T 就像一个占位符、或者说一个变量,在使用的时候可以把定义的类型像参数一样传入,它可以原封不动地输出

泛型在成员之间提供有意义的约束,这些成员可以是:函数参数、函数返回值、类的实例成员、类的方法等。