TS中的泛型是什么(上)

221 阅读4分钟

什么是泛型呢?

练习工具

看过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 类型,非常讨厌。

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)
}) 

fetch数据结构

我对泛型的定义

泛型就是使用一个类型变量来表示一种类型,类型值通常是在使用的时候才会设置。泛型的使用场景非常多,可以在函数、类、interface接口中使用。
泛型简单来说就是类型变量,在TS中存在类型,如number、string、boolean等,它可以轻松解决输入输出要一致的问题。

  • TS 中不建议使用 any 类型,不能保证类型安全,调试时缺乏完整的信息。
  • TS 可以使用泛型来创建可重用的组件(JSX/TSX)。支持当前数据类型,同时也能支持未来的数据类型。扩展灵活,可以在编译时发现类型错误,从而保证了类型安全。

TS中的泛型是什么(下)