什么是泛型呢?
看过TS 文档,一定看过这样两段话:
软件工程中,我们不仅要创建一致的定义良好的 API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
在像 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
说的是人话么?你确定我这种小垃圾看得懂?
我们还是先来看这样一个例子,体会一下泛型解决的问题吧。
function print(arg:string):string {
console.log(arg)
return arg
}
现在需求变了,我还需要打印 number 类型,怎么办?
我的第一反应是通过使用联合类型来改造
function print(arg:string | number):string | number {
console.log(arg)
return arg
}
现在需求又变了,我还需要打印 string 数组、number 数组,甚至任何类型,怎么办?
有个笨方法,支持多少类型就写多少联合类型(打工人何必为难打工人,把参数类型改成万能的any)。
function print(arg:any):any {
console.log(arg)
return arg
}
毕竟在 TS 中 any 类型不友好,so尽量不要写 any,而且这也不是我们想要的结果。只能说传入的值是 any 类型,输出的值是 any 类型,传入和返回并不是统一的。
举个BUG栗子
const res:string[] = print(123)
定义 string 类型数组来接收 print 函数的返回值,返回的是个 number 类型,TS 并不会报错提示我们。
这个时候救世主泛型就出现了,它可以轻松解决输入输出要一致的问题。
泛型基本使用
我们使用泛型来解决上文的问题
我还需要打印 string 数组、number 数组,甚至任何类型,怎么办?
泛型的语法是 <> 里写类型参数,一般可以用 T 来表示 type。
function print<T>(arg:T):T {
console.log(arg)
return arg
}
这样,我们就做到了输入和输出的类型统一,且可以输入输出任何类型。如果类型不统一,就会报错。
泛型中的 T 就像一个占位符、或者说一个变量,在使用的时候可以把定义的类型像参数一样传入,它可以原封不动地输出。
泛型的写法
<>T,但记住就好,只要一看到<>,就知道这是泛型。
两种方式指定类型
-
定义要使用的类型
print<string>('hello') // 定义 T 为 string -
TS 类型推断,自动推导出类型
print('hello') // TS 类型推断,自动推导类型为 string
type 和 interface 都可以定义函数类型
type 泛型
type Print = <T>(arg: T) => T
const printFn:Print = function print(arg) {
console.log(arg)
return arg
}
interface 泛型
interface Iprint<T> {
(arg: T): T
}
function print<T>(arg:T):T {
console.log(arg)
return arg
}
const myPrint: Iprint<number> = print
如果要给泛型加默认参数,可以这么写:
interface Iprint<T = number> {
(arg: T): T
}
function print<T>(arg:T) {
console.log(arg)
return arg
}
const myPrint: Iprint = print
处理多个函数参数
现在有这么一个函数,传入一个只有两项的元组,交换元组的第 0 项和第 1 项,返回这个元组。
function swap(tuple) {
return [tuple[1], tuple[0]]
}
我们用 T 代表第 0 项的类型,用 U 代表第 1 项的类型。
function swap<T, U>(tuple: [T, U]): [U, T]{
return [tuple[1], tuple[0]]
}
这样就可以实现了元组第 0 项和第 1 项类型的控制。
函数副作用操作
泛型不仅可以很方便地约束函数的参数类型,还可以用在函数执行副作用操作的时候。
比如我们有一个通用的异步请求方法,想根据不同的 url 请求返回不同类型的数据。
举个例子
request('user/info').then(res =>{
console.log(res)
})
这时候的返回结果 res 就是一个 any 类型,非常讨厌。
我们希望调用 API 都清晰的知道返回类型是什么数据结构,就可以这么做:
interface UserInfo {
name: string
age: number
}
function request<T>(url:string): Promise<T> {
return fetch(url).then(res => res.json())
}
request<UserInfo>('user/info').then(res =>{
console.log(res)
})
我对泛型的定义
泛型就是使用一个类型变量来表示一种类型,类型值通常是在使用的时候才会设置。泛型的使用场景非常多,可以在函数、类、interface接口中使用。
泛型简单来说就是类型变量,在TS中存在类型,如number、string、boolean等,它可以轻松解决输入输出要一致的问题。
- TS 中不建议使用 any 类型,不能保证类型安全,调试时缺乏完整的信息。
- TS 可以使用泛型来创建可重用的组件(JSX/TSX)。支持当前数据类型,同时也能支持未来的数据类型。扩展灵活,可以在编译时发现类型错误,从而保证了类型安全。