TS <T> 函数泛型
一、泛型的定义
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
举个例子,一个函数接受一个 number
参数并返回一个 number
参数,写法如下:
function returnItem(para: number): number {
return para
}
如果想接受一个 string
类型,再返回 string
类型,写法如下:
function returnItem(para: string): string {
return para
}
以上编写方式存在代码重复度较高的问题,虽然可以用 any
类型替代,但目的是接收什么类型的参数返回什么类型的参数,即在运行时传参后才能确定类型,这时就可以使用泛型,如下:
function returnItem<T>(para: T): T {
return para
}
可以看到,泛型给予开发者创造灵活、可重用代码的能力。
二、泛型的使用
泛型通过 <>
的形式进行表述,可以声明:
- 函数
- 接口
- 类
1、函数声明
声明函数的形式如下:
function returnItem<T>(para: T): T {
return para
}
定义泛型的时候,可以一次定义多个类型参数,比如同时定义泛型 T
和 泛型 U
:
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]]
}
swap([7, 'seven']) // ['seven', 7]
2、接口声明
声明接口的形式如下:
interface ReturnItemFn<T> {
(para: T): T
}
当想传入一个 number
作为参数时,可以这样声明函数:
const returnItem: ReturnItemFn<number> = (para) => para
3、类声明
使用泛型声明类时,既可以作用于类本身,也可以作用于类的成员函数。
举个例子,实现一个元素同类型的栈结构:
class Stack<T> {
private arr: T[] = []
public push(item: T) {
this.arr.push(item)
}
public pop() {
this.arr.pop()
}
}
使用方式如下:
const stack = new Stack<number>()
如果上述只能传递 string
和 number
类型,可以使用 <T extends xx>
的方式猜实现约束泛型,如下所示:
泛型更高级的使用场景如下:
例如要设计一个函数,这个函数接受两个参数,一个参数为对象,另一个参数为对象上的属性,通过这两个参数返回这个属性的值;这时就涉及到泛型的索引类型和约束类型共同实现。
3、索引类型、约束类型
索引类型 keyof T
把传入对象的属性类型取出生成一个联合类型,这里的泛型 U 被约束在这个联合类型中,如下所示:
function getValue<T extends object, U extends keyof T>(obj: T, key: U) {
return obj[key] // ok
}
为什么需要使用泛型约束,而不是直接定义第一个参数为 object
类型呢?
因为默认情况 object
指的是 {}
,而接收的对象是各种各样的,一个泛型来表示传入的对象类型,比如 T extends object
使用如下图所示:
4、多类型约束
下面要实现两个接口的类型约束:
interface FirstInterface {
doSomething(): number
}
interface SecondInterface {
doSomethingElse(): string
}
可以创建一个接口继承上述两个接口,如下:
interface ChildInterface extends FirstInterface, SecondInterface {
}
正确使用如下:
class Demo<T extends ChildInterface> {
private genericProperty: T
constructor(genericProperty: T) {
this.genericProperty = genericProperty
}
useT() {
this.genericProperty.doSomething()
this.genericProperty.doSomethingElse()
}
}
通过泛型约束就可以达到多类型约束的目的。