泛型(Generics),即 类型参数化
。如果对某个参数的类型不明确,就可以使用泛型代替。
现在想要定义一个简单的 identity
方法,接收 number
类型参数, 返回参数本身:
function identity(arg: number): number {
return arg
}
假设现在不明确参数 arg
的类型,可以使用 any
类型来定义:
function identity(arg: any): any {
return arg
}
这样写存在问题,返回可以是 any
类型,不一定是参数 arg
的类型,同时也丢失了 arg
类型信息。
此时可以使用泛型来重写该方法:
function identity<T>(arg: T): T {
return arg
}
函数名后加上 <T>
来表明该方法是 泛型方法
,T
是一个 类型参数
(这里就是将类型定义成变量 T
了,不需要写死类型为 number
等明确类型),类型参数可以定义为任意字符,常用的还有 K
、V
、E
等,方法中可以使用定义过的类型参数作为类型使用。
给 identity
添加类型参数 T
之后, T
帮助我们捕获用户传入的类型(如:number
),之后便可以使用这个类型。
使用泛型函数
let output = identity<string>('myString')
let output = identity('myString')
identity
方法中想要打印 arg
参数的长度,可能会这么做:
function identity<T>(arg: T): T {
console.log(arg.length)
return arg
}
这样是错误的,T
要当作通用类型看待,而如果传入的是 number
类型,是没有 length
属性的。
function identity<T>(arg: T[]): T[] {
console.log(arg.length)
return arg
}
或者
function identity<T>(arg: Array<T>): Array<T> {
console.log(arg.length)
return arg
}
泛型函数类型和普通函数类型类似,只需要在前面加上 类型参数
:
function identity<T>(arg: T): T {}
let foo: <U>(arg: U) => U // 类型参数只需要对应即可,不必须为T
foo = identity
还可以使用 带有调用签名的对象字面量
来定义泛型函数:
let foo: { <T>(arg: T): T }
foo = identity
把泛型类型的对象字面量形式拿出来作为一个接口即可:
interface IdentityInterface {
<T>(arg: T): T
}
let foo: IdentityInterface = identity
泛型的类型参数还可以定义在接口名后面,作为整个接口的参数,来锁定整个接口中使用的类型:
interface GenericIdentityInterface<T> {
(arg: T): T
}
function identity<T>(arg: T): T {}
let foo: GenericIdentityInterface<number>
foo = 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
}
console.log(myGenericNumber.add(myGenericNumber.zeroValue, 3)) // 3, 0 + 3
let stringNumeric = new GenericNumber<string>()
stringNumeric.zeroValue = ''
stringNumeric.add = function(x, y) {
return x + y
}
console.log(stringNumeric.add(stringNumeric.zeroValue, 'abc')) // 'abc', '' + 'abc'
与泛型接口一样,将类型参数放在泛型类的类名后面,可以帮助确认类中所使用的类型。
注意: 类有静态部分(包括静态属性,静态方法,类的构造方法)和实例部分。而泛型只能用于类的实例部分。
即对泛型的类型参数进行约束。例如之前的例子:
function identity<T>(arg: T): T {
return arg
}
如果想要打印 arg.length
会报错,因为泛型 T
不一定具有 length
属性,此时可以让 T extends
某个接口,来强制约束 T
具有该属性:
interface Lengthwise {
length: number
}
function identity<T extends Lengthwise>(arg: T): T {
console.log(arg.length)
return arg
}
现在这个泛型函数被定义了约束,传入的参数必须具有 length
属性:
identity(3) // Error, number doesn't have a .length property
identity({ value: 3, length: 1}) // OK
可以声明一个类型参数,而它被另一个类型参数约束。
比如,想要用属性名从一个对象上获取该属性的值,并且想要确保属性名一定存在于该对象上,此时就需要在两个类型参数间使用约束:
function getProperty<T, K extends keyof T>(obj: T, key: K): any {
return obj[key]
}
let obj = {
a: 1,
b: 'string',
c: true,
d: null
}
getProperty(obj, 'b')
// getProperty(obj, 'm') // Error
例如,想要定义一个 createInstance
函数创建特定的类的实例:
function createInstance<T>(c: new () => T): T {
return new c()
}
更进一步,综合使用 泛型约束
与 类类型
,来限定创建的实例的类型:
interface Animal {
name: string
}
class Cat implements Animal {
name: string = 'cat'
}
class Mouse implements Animal {
name: string = 'mouse'
}
function createInstance<T extends Animal>(c: new () => T): T {
return new c()
}
createInstance(Cat).name // 'cat'
createInstance(Mouse).name // 'mouse'