美文网首页
TypeScript类型体操--挑战Fill工具类型

TypeScript类型体操--挑战Fill工具类型

作者: 习惯水文的前端苏 | 来源:发表于2023-06-20 17:45 被阅读0次

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

前言

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

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

image.png

提示

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

使用示例

  • 示例一

按照参数二逐一进行替换

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

按照参数3替换指定位置

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

将区间[2,4)的元素进行替换

type Result = Fill<[1, 2, 3, 4], 'a',2,4> // [1, 'a', 'a', 4]

实现

目前为止,我们实现的都是一些简单的工具类型,为了不让大家小看TypeScript,我这次稍微提升了下难度。现在我们暂停花两分钟,结合使用示例先自己思考一下

image.png

版本一

首先我们要确认下泛型参数的个数,它至少应该有T、U两个参数:T表示数组本身、U表示要替换填充的值

type Fill<T,U>

接着我们来考虑泛型参数的限制条件,显然,T必须是数组类型,U则可以是any类型不需要约束

type Fill<T extends any[],U>

在只有两个参数的情况下,该版本功能上实际就是拿U去一个一个替换掉T中的元素,至于每一个元素的类型是啥,其实都无所谓,所以我们先把原数据进行拆分,拿到数组中的第一个元素

[F,...R] 

我们只需要将F替换成U,就完成了在索引为0位置的替换

[U,...R] 

下一次,我们只需要继续对L进行拆分成F和R,并使用U替换F即可

type Fill<T extends any[],U> = T extends [infer F,...infer R] ? [U,...Fill<R,U>] : T

使用示例

image.png

但是目前我们的F类型是多余的,它被显示为暗淡色,尽管它不影响,但是看着别扭,所以我们使用T[0]来代替

image.png

版本二

现在我们来考虑参数三,它代表索引位置,因此我们使用I来表示

type Fill<T extends any[],U,I> 

同时我们对其进行约束,使其必须是number类型,并且它应是可选的

type Fill<T extends any[],U,I extends number = 0> 

在版本一中,我们相当于每次固定的去替换了索引为0的位置,由于I是一个动态的值,因此我们不得不进行判断,以确定什么时候应该应用U,关于判断本身也不难

2 extends 3 ? true : false

在TypeScript中跟位置相关的有number和length,但前者是用来获取元素的,故排除掉,就只剩下length了,但这表示的是数组的长度,乍一看似乎跟I关联不上...

不过再仔细想想,我们在递归过程中,不就是每次都少一位吗?

image.png
T['length'] extends I ? '匹配到索引位置I' : '未匹配到'

现在我们来分析两个分支:

  • 匹配到时

由于的T['length']是在不断减少的,所以我们应该将最后一位替换成U,剩下的接着丢去递归

[...Fill<F,U,I>,U]
  • 不匹配时

我们不需要做任何处理,将一开始拆出来的,再原封不动的拼接回去就好了

[...Fill<F,U,I>,L]

综上所述,最终实现如下

type Fill<
  T extends unknown[],
  U,
  I extends number = 0,
> = T extends [...infer F,infer L]
  ? T['length'] extends I 
    ? [...Fill<F,U,I>,U]
    : [...Fill<F,U,I>,L]
  : T

使用示例

image.png

版本三

版本二的实现虽然满足了示例二,但会导致示例一失败,因此,我们还需要对其进行下兼容

image.png

我们观察版本二与版本一的区别,主要体现在拼回时是取原值L还是U进行替换,而这又恰好与I的取值有关,当I为0时,说明取得是默认值,此时我们切换为取U就好了

type Fill<
  T extends unknown[],
  U,
  I extends number = 0,
> = T extends [...infer F,infer L]
  ? T['length'] extends I 
    ? [...Fill<F,U,I>,U]
    : [...Fill<F,U,I>, I extends 0 ? U : L]
  : T

使用示例

image.png

版本四

现在,我们来考虑参数四,它将与参数三组成一个前闭后开的范围,为了更好的语意,我们将I修改为S,表示start开始位置

type Fill<
  T extends unknown[],
  U,
  S extends number = 0,
  E
>

E的类型和S相同,不过默认值要有差别,它应该是T['length']

type Fill<
  T extends unknown[],
  U,
  S extends number = 0,
  E extends number = T['length'],
>

比较容易想到的是,它应该也必须和I一样,通过构建条件类型来进行判断,因此,我们必须构建一个类型来对T['length']去反

为此,我们需要声明一个类型变量用于记录当前已经处理了几个元素,这里我们使用C来表示count计数

type Fill<
  T extends unknown[],
  U,
  S extends number = 0,
  E extends number = T['length'],
  C extends any[] = []
>

接着在递归调用处不断的给C添加新的元素,此时,每一轮的C['length'] + T['length']都刚好等于初始时的T['length']

Fill<F,U,I,[...C,any]>

现在我们来为递归找出口,即当C['length']与E相等时,后续的元素都不需要再进行处理了

C['length'] extends E ? T : '待处理'

现在,我们接着思考:如果当遇到S时我们能够有一个变量来标记开始,然后在遇到E前,都强制取U,是不是就正好是我们想要的功能了?为此,我们需要声明一个类型变量M来进行标记,并且它默认与S进行比较

type Fill<
  ...
  M extends boolean = C['length'] extends S ? true : false
>

此时,当遇到S时,我们标记为true,后续的递归全部沿用此标记,强制取U就可以了。另外,由于我们已经将出口前置,所以是不需要做将M切回false的处理的

故实现代码如下

type Fill<
  T extends unknown[],
  U,
  S extends number = 0,
  E extends number = T['length'],
  C extends any[] = [U],
  M extends boolean = C['length'] extends S ? true : false
> = C['length'] extends E 
?T
:T extends [infer F , ... infer R]
  ?M extends true
    ?[U,...Fill<R,U,S,E,[...C,any],M>]
    :[F,...Fill<R,U,S,E,[...C,any]>]
  :T

使用示例

image.png

版本五

对于版本四,它对示例三正按照预期进行工作,但是其无法兼容示例一和示例二,这其实也不难实现,我们只需要将示例二作为单独的工具类型,并在符合条件时进行调用就可以啦

type Fill<
  T extends unknown[],
  U,
  S extends number = 0,
  E extends number = T['length'],
  C extends any[] = S extends 0 ? [] : E extends T['length'] ? [] : [U],
  M extends boolean = C['length'] extends S ? true : false,
  L extends boolean =  C extends [] ? true : false
> = C['length'] extends E 
?T
:T extends [... infer R,infer F ]
  ? L extends true
    ? Comppat<T,U,S>
    :M extends true
      ?[...Fill<R,U,S,E,[...C,any],M>,U]
      :[...Fill<R,U,S,E,[...C,any]>,F]
  :T

type Comppat<
  T extends unknown[],
  U,
  I extends number = 0,
> = T extends [...infer F,infer L]
  ? T['length'] extends I 
    ? [...Comppat<F,U,I>,U]
    : [...Comppat<F,U,I>, I extends 0 ? U : L]
  : T

下期预告

Reverse

  • 功能

将数组类型中的元素进行翻转

  • 使用示例
type Res = Reverse<[1,2]> // [2,1]

Filter

  • 功能

从数组类型中挑选指定的类型并返回一个由挑选类型组成的新数组

  • 使用示例
type Res = Filter<[1,2],2> // [2]

如果本文对您有用,希望能得到您的点赞和收藏

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


相关文章

网友评论

      本文标题:TypeScript类型体操--挑战Fill工具类型

      本文链接:https://www.haomeiwen.com/subject/xoayydtx.html