TypeScript类型体操--isTuple、lastIndexOf、includes

190 阅读4分钟

大家好,我是苏先生,一名热爱钻研、乐于分享的前端工程师,跟大家分享一句我很喜欢的话:人活着,其实就是一种心态,你若觉得快乐,幸福便无处不在

前言

前边三篇文章我们一共实现了22个工具类型,按照本专栏的规划,还差77个...

本节我们继续学习数组相关的类型编程

image.png

提示

对于语法层面的知识点本系列(类型体操开头的标题)不会展开说明哈,可以自行搜索学习其他大佬的优质文章或者等我后续更新补充

isTuple

  • 功能

判断一个类型是否是元组

  • 实现

首先它接收一个泛型参数T

type IsTuple<T>

我们不应该限制T的类型,如果T是一个非元组,则直接返回false就好了

type IsTuple<T> = T extends any[] ? true : false

目前,对于readonly修饰符,我们的IsTuple会返回false

image.png

要解决这个问题,我们只需要将条件类型相应的增加readonly修饰符就可以了,这是因为带有修饰符的元组表示的范围更小,你可以认为它是没有修饰符的父类型

image.png

最后,我们思考数组和元组的区别,看下图,一个数组类型的length的返回类型为number

image.png

但是对于元组,length取到的却是一个具体的数字

image.png

为此,我们需要对length进行单独的处理

type IsTuple<T> = T extends readonly any[] ? 
                    number extends T['length'] 
                    ?
                    false
                    : true
                    : false
  • 使用示例

image.png

lastIndexOf

  • 功能

找到元素在元组中的位置,找不到返回-1

  • 实现

首先,它接收两个泛型参数T、U

type LastIndexOf<T,U>

接着,我们对泛型参数进行约束:对于泛型T,应该是一个数组类型,而泛型U则是any类型

type LastIndexOf<T exyends any[],U>

然后,我们要想办法拿到最后一个元素,在上一节中我们介绍过,通过扩展运算可以达到该目的,如下,L即是最后一个元素,此时我们只需要借助infer关键字就可以拿到元素对应的类型了

[...infer F,infer L]

既然我们已经拿到了最后一个元素,那就可以与U进行比较判断了,我们知道,当两个类型一样或具有父子关系时,通过extends可以得到true,伪代码如下

L extends U ? '得到其对应的位置' : '递归'

我们分别来思考extends分两个分支:

1-成立时,取位置

上一节我们在分析Last工具类型时介绍过,T["length"]能够取到当前数组的长度,且它与元素对应的位置的T["length"]的取值减去1,这恰好是F的长度

2-不成立时,进行递归

这个比较简单,我们只需要将数组的最后一位删掉重新调用LastIndexOf就行了,好巧不巧的是,它又刚好是F

综上所述,最终实现如下

type LastIndexOf<T extends any[],U> = T extends [...infer F,infer L] ? 
                                        L extends U 
                                            ?
                                            F['length']
                                            : LastIndexOf<F,U>
                                        : -1
  • 使用示例

image.png

includes

  • 功能

判断元素是否在元组中

  • 实现

首先,它接收两个泛型参数T、U

type Includes<T,U>

泛型T的参数需要约束为数组类型

type Includes<T extends any[],U> 

接着我们要想办法拿到数组中的每一个元素与U进行比较,正如前文分析的那样,这肯定要用到类型递归

T extends [...infer F,infer L] ? '判断是否存在' : '递归'

事实上,按照上文LastIndexOf的实现思路这会变得很容易,不过由于多个条件类型会让代码变得难以阅读。所以,我们选择提取出一个公共类型来专门负责两个元素的比较

type IsEqual<V,Item> = V extends Item ? true : false

最后按照LastIndexOf的套路,我们比较完一个,就把已经比较过的元素丢弃掉,并重新调用includes,最终实现如下

type Includes<Value extends any[], Item> =
	IsEqual<Value[0], Item> extends true
		? true
		: Value extends [Value[0], ...infer rest]
			? Includes<rest, Item>
			: false;
  • 使用示例

image.png

下期预告

最后一个就当作是一个随堂练习啦,我们放在下一节中进行实现(ps:欢迎在评论区晒出你的实现思路🤩)

Fill

  • 功能

类型版本的数组填充

  • 示例
type Result = Fill<[1, 2, 3], 'a'> // ['a', 'a', 'a']
type Result2 = Fill<[1, 2, 3], 'a',2> // [1, 'a', 3]
type Result3 = Fill<[1, 2, 3], 'a',1,2> // [1, 'a', 3]
  • 提示

类型递归


如果本文对您有用,希望能得到您的❤

订阅专栏,每周更新2-3篇类型体操,每月1-3篇vue3源码解析,等你哟😎


github与好文