欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

TypeScript中的泛型

程序员文章站 2022-07-03 19:15:27
...

原文链接: https://www.suxuewb.cn/?p=276

泛型的概念

一般的计算机设计语言都有泛型的概念。泛型允许程序员在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型。这个“以后”,可以是编译期间,也可以是运行时。

一个例子

有以下功能函数:

function log(data){
   console.log(data)
   return data
}

log 函数接收数据,打印并返回该数据。当我们使用TS时,需要指定data参数的类型,函数返回的类型:

function log(data: string): string {
	console.log(data)
	return data
}

执行log('1'),会正常输出,但时当你执行log(1)时,系统提示你,log函数应当传入字符串,而不是数字。而我们的这个函数,当然不仅仅想只能够在字符串string下使用。

我们想要的是,传入什么类型数据,就返回什么类型数据。

其他类型

想要在data为number类型下成立,只需定义logNumber

function logNumber(data: number): number {
	console.log(data)
	return data
}

难道有多少种类型,我就要写多少个函数声明吗?那不得累死。

危险的做法

上述问题有一种危险做法,之所以是危险,因为它不规范,违背了TS的设计原则:

function logNumber(data: any): any {
	console.log(data)
	return data
}

把类型,声明为any。这和写js没什么区别了。

泛型的做法

在看泛型做法之前,我想先介绍一下思路,我认为这是理解泛型的关键。

回看问题的关键,**我们想要的是,传入什么类型数据,就返回什么类型数据。**也就是说,返回类型是由传入类型决定。这样理解,我们就可以用函数来表示传入和返回之间的关系。

假设返回的类型为Z,传入的类型为T,则满足Z=f(T)。其中f,代表变换法则。换句话说,Z和T之间存在映射关系。

在log函数中,假如用js,来描述log函数返回值类型的类型变换过程:

function f(T) {
	return T
}

其中,T表示传入的类型。

接下来,我们用TS写log函数:

function log<T>(data: T): T {
	console.log(data)
	return data
}

我来解读一下,尖括号定义T(实际上可以是任何字母)变量,然后把data参数的类型声明为T,那么T表示传入参数的类型。至此,函数体里面任何地方都可以使用T。最后声明返回值的类型为T。

实际上,完成了声明类型变量,并赋值类型以后,每次出现T的地方,都可在满足Z=f(T)的条件下,使用T。我们log函数的例子只有返回值类型用到了T。

更多

改变一下log函数的功能:

function log(data){
   console.log(data)
   return [data]
}

它的TS怎么写呢,上一个功能,我们Z=f(T)变换是T=>T,这一次,它是T=>Array<T>:

function log<T>(data: T): Array<T> {
	console.log(data)
	return [data]
}

泛型只是声明一个类型变量,并把指定类型赋值给该变量,方便以后使用。是不是很容易理解呢?

例子的延伸

Z=f(T)中Z代表返回类型,现在,我们把它扩展到任意类型,且不局限于在函数内。

上面说,Z=f(T)变换由T=>T变成了T=>Array<T>。在这里,Array是一个内置的类型,事实上,它可以是任何类型。

定义一个泛型接口:

interface BodyObj <T>{
	body: T
}

这是什么?这是一个泛型接口,你可以看成一个“类型函数”。该类型函数,有一个参数T,其内部根据T,做了一些类型约束。看上去,这更像是类型系统的抽象复用手段。你可以这么使用:

let bodyObj: BodyObj<string> = {
	body: 'ssss'
}

又或者:

let bodyObj: BodyObj<Array<string>> = {
	body: ['ssss']
}

在这里,Z=f(T)变换是T=>BodyObj<T>

我认为,泛型为类型系统提供了Z=f(T)的功能,以便于类型的抽象复用,功能完善。

复杂的例子

来看以下函数:

function unique(arr, u_key) {
  const map = new Map()
  arr.forEach((item) => {
    if (!map.has(item[u_key])) {
      map.set(item[u_key], item)
    }
  })
  return [...map.values()]
}

该函数用于对Array<object>,根据object中指定的key的值,进行去重。他的TS声明什么写呢?大部分人会这么写:

export declare function unique<T, K>(arr: T, u_key: K): T

或者

export declare function unique<T, K>(arr: Array<T>, u_key: K): Array<T>

这样写缺少了很多,比如,Array<T>中的T必须是Object,K必须是T的一个key。

你应该这么写:

export declare function unique<T extends object, K extends keyof T>(arr: Array<T>, u_key: K): Array<T>

我们用了extends和keyof关键词,extends意为继承、满足,keyof意味是…的key。

首先,我们声明了T满足object,即T是对象,又声明了K,是T的一个key。并把函数调用传递过来的类型,抽取出arr参数Array<object>类型中的object赋值给T,u_key的类型赋值给K,同时返回Array<T>
这样,在调用时,会检查你传递的u_key是不是arr中对象的key,以确保函数正常运行,是不是很棒呢?

总结

我认为,泛型就是类型函数,即Z=f(T)