精读TypeScript 类型体操练习 之 Includes

·  阅读 98

ts有时候遇到实际问题,看官方文档也是云里雾里的。基础能力很重要,学会了基础能力,但实际运用又不一定能想到这么用。解决问题最好的办法就是多思考多练,通过实际案例不断刺激大脑,养成ts思维习惯。推荐github上类型体操type challenges,空余时间有事没事练一两道题。

接着上一期,这一篇章精讲一下Includes

Includes

题目: 用类型系统实现Includes<T, K>函数:

在js中

includes可以判断一个数组中是否包含某一个元素,并返回true或false

['a', 'b', 'c'].includes('a') // true

['a', 'b', 'c'].includes(1) // false
复制代码

includes可以判断一个字符串中是否包含指定的子字符串,并返回true或false

const str = "Hello world"
str.includes('world') // true
复制代码

回到ts中

type Includes<T extends readonly any[], U> = any

所以当时一开始就想只要U是T的子集,U包含于TT extends U,对这个条件满足的就是true,不满足的就是false

type Includes<T extends readonly any[], U> = T extends U ? true : false
复制代码

结果一运行, 三元运算全部走到false,也就是说T extends U是不成立的。
我们知道extends在ts类型当中一般起对传入的参数做一个限制,也就是说T必须含有U。

T extends U 为什么不成立?

T由于是数组类型,extends不支持判定数组包含逻辑。

循环一个一个出来对比

那既然T extends U ? true : false走不通,那可不可把T循环一个一个元素和U对比呢?,如果U extends V(V: 代表循环出来的T的一个一个元素,也就是T的任意一项), 这样不就符合了吗?说干就干,但是typescript中数组的循环应该怎么表示呢?Stack overflow 表示the element type of the array as T[number]数组的元素类型为T[number]

那么答案这么写看看过不过:

type Includes<T extends readonly any[], U> = U extends T[number] ? true : false
复制代码

测试的案例如下:

截屏2022-07-12下午4.17.23.png

红色波浪线的没有通过测试

第一:

Expect<Equal<Includes<[boolean, 2, 3, 5, 6, 7], false>, false>>,
Expect<Equal<Includes<[true, 2, 3, 5, 6, 7], boolean>, false>>,
复制代码

false exetnds boolean // false 继承了 boolean, 所以是true

boolean extends true // boolean 有可能是true也有可能是false,所以返回的依旧是boolean

第二:

Expect<Equal<Includes<[{}], { a: 'A' }>, false>>,
复制代码

{ a: 'A' } extends {}

断定为 true,然而需要的结果是false

第三:

Expect<Equal<Includes<[{ a: 'A' }], { readonly a: 'A' }>, false>>,
Expect<Equal<Includes<[{ readonly a: 'A' }], { a: 'A' }>, false>>,
复制代码

{ readonly a: 'A' } extends { a: 'A' } // { readonly a: 'A' }继承了{ a: 'A' },所以是true

{ a: 'A' } extends { readonly a: 'A' } // 反过来{ a: 'A' }继承了{ readonly a: 'A' },所以也是true

第四:

Expect<Equal<Includes<[1], 1 | 2>, false>>,
Expect<Equal<Includes<[1 | 2], 1>, false>>,
复制代码

1 | 2 extends 1 // 1 既可以说是继承了 1 | 2, 也可以说不是,这个得看是1 还是2,所以返回的既是true也是false,也就是boolean

1 extends 1 | 2 // 1 | 2 肯定是继承了 1, 返回true


排查上面两种猜想答案,接下来看看递归来对比

一个一个来对比。

首先先说一下对比,怎么个对比法?其实就是看两者是否相等。

其实说到底这道题要么是true要么就是false,跑不了这两个答案。于是索性就把true的案例放一边,false案例放一边。

截屏2022-07-12下午5.31.08.png

type Includes<T extends readonly any[], U> = any

||

type Includes<T extends readonly any[], U> =
T extends [infer First, ...infer Rest]
?
true
:
false
复制代码

如上,既然需要一个一个对比,那么必须解构出来,从首个开始进行比较。那问题来了,解构出来的首个和谁进行比较,当然是First和U进行比较。

假设有个比较的判断类型

Equal
复制代码

那么下面的这段可以改写为:

type Includes<T extends readonly any[], U> =
T extends [infer First, ...infer Rest]
?
true
:
false

||

type Includes<T extends readonly any[], U> =
T extends [infer First, ...infer Rest]
?
Equal<First, U> extends true
    ? true
    : Includes<Rest, U>
:
false
// 如果First也就是第一个就是true那么整体就是true,如果不成立那就递归,把剩下的递归给判断掉
复制代码

那么传入X和Y的Equal类型改如何呢?

首先先确定了,相比较两个数相等与否,就是两个参数, 设定为X、Y

Equal<X, Y> = any
复制代码

接下来就是比较了

如果 X extends Y 那就是true,不是就是不相等也就是false
复制代码
Equal<X, Y> = X extends Y ? true : false
复制代码

Equal类型这样就结束了吗?等等,还没有。

由于上面我们提到的情况

Expect<Equal<Includes<[true, 2, 3, 5, 6, 7], boolean>, false>>,
Expect<Equal<Includes<[{ a: 'A' }], { readonly a: 'A' }>, false>>,
Expect<Equal<Includes<[{ readonly a: 'A' }], { a: 'A' }>, false>>,
Expect<Equal<Includes<[1], 1 | 2>, false>>,
复制代码

当X是true时、Y是boolean时,true exetnds boolean, true 是继承了 boolean, 所以是true,当是boolean并非包含在[true, 2, 3, 5, 6, 7]中的,所以这个导致结果返回true,但要求是false,所以没有通过。

同理也就可以解释情况[{ a: 'A' }], { readonly a: 'A' },情况[{ readonly a: 'A' }], { a: 'A' },情况[1], 1 | 2

那Equal怎么改才正确呢?

type Equal<X, Y> = 
() extends () ? true : false

||

() // 括号里的写法

(<Res>() => Res extends X ? true : false) // 这样的写法也就是说用固定的true 或者 false 去跟 true 或者 false去对比是不是继承关系。

type Equal<X, Y> =
(<Res>() => Res extends X ? true : false)
extends
(<Res>() => Res extends Y ? true : false)
?
true
:
false
复制代码

最终答案

type Equal<X, Y> = (<Res>() => Res extends X ? ture : false) extends (<Res>() => Res extends Y ? true : false) ? true : false

type Includes<T extends readonly any[], U> = 
T extends [infer First, ...infer Rest]
 ?
     (
         Equal<First, U> extends true
         ? true
         : Includes<Rest, U>
     )
 :
 false
复制代码

总结

这道题用到了递归解构。这道题github上放到的是easy那一块,导致我以为确实是easy,一开始没往太复杂的写法去想。导致解了好久,案例一直不过,卡了一段时间。type-challenges做题系列的文章断了好几天。这题真的不easy。多做题多思考,在实际项目中活学活用才能真正拿捏。

分类:
前端
收藏成功!
已添加到「」, 点击更改