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

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

前言

前边三篇文章我们一共实现了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源码解析,等你哟😎


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容