题目
在类型系统里实现 JavaScript 的 Array.includes
方法,这个类型接受两个参数,返回的类型要么是 true
要么是 false
。
例如:
ts
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'>
// expected to be `false`
测试 Case:
ts
type cases = [
Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'>, true>>,
Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'>, false>>,
Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 7>, true>>,
Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 4>, false>>,
Expect<Equal<Includes<[1, 2, 3], 2>, true>>,
Expect<Equal<Includes<[1, 2, 3], 1>, true>>,
Expect<Equal<Includes<[{}], { a: 'A' }>, false>>,
Expect<Equal<Includes<[boolean, 2, 3, 5, 6, 7], false>, false>>,
Expect<Equal<Includes<[true, 2, 3, 5, 6, 7], boolean>, false>>,
Expect<Equal<Includes<[false, 2, 3, 5, 6, 7], false>, true>>,
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>>,
Expect<Equal<Includes<[1 | 2], 1>, false>>,
Expect<Equal<Includes<[null], undefined>, false>>,
Expect<Equal<Includes<[undefined], null>, false>>,
]
初始代码:
ts
type Includes<T extends readonly any[], U> = any
题解
我第一反应想到的,可以通过条件类型来判断 U
是否可以赋值给 T
中的元素。通过 T[number]
来获取 T
中所有元素的联合类型:
ts
type Includes<T extends readonly any[], U> = U extends T[number] ? true : false;
不过这样错了,比如下面的例子:
ts
Expect<Equal<Includes<[{}], { a: 'A' }>, false>>,
虽然 { a: 'A' }
可以赋值给 {}
,但是不代表两者相等。
现在,我们需要一个方案来判断两个类型是否相等,虽然这有点难,不过早已经有前辈写好了:
ts
export type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
? true
: false
原理有点复杂,准备后面单独写一篇文章来介绍。总计记住就好了。
接下来就可以实现 Includes
了
ts
type Includes<T extends readonly unknown[], U> = T extends [infer First, ...infer Rest]
? Equal<First, U> extends true ? true : Includes<Rest, U>
: false;
我们先判断第一个元素是否为查找类型,然后再判断剩余的数组中是否包含该类型,递归解决问题。
这里面涉及的知识点前面有讲到过,这里再复习一下:
T extends readonly unknown[]
类型约束,T
被限制为一个只读数组或元组,其元素类型是unknown
T extends [infer First, ...infer Rest]
:这是一个条件类型,它检查T
是否可以赋值给一个元组类型,其中第一个元素被推断为First
,剩余的元素被推断为Rest
。如果T
是空数组或空元组,这个条件将不成立。infer First
和...infer Rest
:infer
关键字在这里用来在条件类型的上下文中推断元组的第一个元素的类型(First
)和剩余元素的数组类型(Rest
)Equal<First, U> extends true ? true : Includes<Rest, U>
:如果T
可以赋值给[infer First, ...infer Rest]
,则进一步检查First
元素是否与类型U
相等。这通过另一个假设存在的类型别名Equal
来完成,它应该能够检查两个类型是否相等。如果First
和U
相等,结果是true
。如果不相等,递归地调用Includes<Rest, U>
来检查剩余的元素。: false
:如果T
不是一个非空元组或数组,或者First
和U
不相等且Rest
为空数组,则Includes
类型解析为false
。
这样,我们就可以通过 Includes
来判断一个类型是否包含在某个元祖类型中了。