Typescript类型体操 - LastIndexOf
题目
中文
实现类型的 Array.lastIndexOf, LastIndexOf<T, U> 接受泛型参数 Array T 和 any U 并返回数组 T 中最后一个 U 的索引
示例:
type Res1 = LastIndexOf<[1, 2, 3, 2, 1], 2>; // 3
type Res2 = LastIndexOf<[0, 0, 0], 2>; // -1
English
Implement the type version of Array.lastIndexOf, LastIndexOf<T, U> takes an Array T, any U and returns the index of the last U in Array T
For example:
type Res1 = LastIndexOf<[1, 2, 3, 2, 1], 2>; // 3
type Res2 = LastIndexOf<[0, 0, 0], 2>; // -1
答案
type LastIndexOf<T extends any[], U> = T extends [...infer L, infer R]
? (<F>() => F extends R ? 1 : 0) extends <F>() => F extends U ? 1 : 0
? L['length']
: LastIndexOf<L, U>
: -1;
此题与5153 - IndexOf
同源, 重点在于比较两个类型是否一致, github 上 #27024 中探讨了这个问题, 核心就是判断两个类型 X
Y
是否完全相同
我最开始想到的做法:
type EqualTo<X, Y> = [X] extends [Y] ? ([Y] extends [X] ? true : false) : false;
这种方法显然有大问题, 比如输入 any
和 1
, 期望的结果是 false
,实际结果确是 true
然后看到的一个比较容易理解的做法:
type EqualTo<X, Y> = [X extends Y ? 1 : 0, Y extends X ? 1 : 0] ? [1, 1]
然后这个写法能处理 99% 的场景了, 但是如果输入的是 any
和 unknown
, 那么实际会返回 true
, 与期望的 false
不符, 这是为什么呢? 因为根据 typescript 的官方文档, 任何一个类型都可以赋值给 unknown
(包括 any
), 而 unknown
只能赋值给它本身和 any
类型的变量, 所以 any extends unknown ? 1 : 0
和 unknown extends any ? 1 : 0
返回结果相同都是 1
, 那么自然 [any extends unknown ? 1 : 0, unknown extends any ? 1 : 0] extends [1, 1]
就是 true
了.
最后经过一番搜索发现了下面这种写法:
type EqualTo<X, Y> = (<F>() => F extends X ? 1 : 0) extends <F>() => F extends Y ? 1 : 0
这种写法下 any
和 unknown
输入的情况的终于也能正常返回期望的 false
. 那为什么这样写有用呢? 在 github issue#27024 看到的解释:
AFAIK it relies on conditional types being deferred when T is not known. Assignability of deferred conditional types relies on an internal isTypeIdenticalTo check, which is only true for two conditional types if:
- Both conditional types have the same constraint
- The true and false branches of both conditions are the same type
这段话的意思大概是:
这段代码起作用主要依靠
F
类型未知时存在延迟的条件类型(上面的F extends X ? 1 : 0
); 延迟条件类型的 可转让性(Assignability) 依赖于一个内部的isTypeIdenticalTo
方法进行检查,isTypeIdenticalTo
只在两个条件类型满足下面的条件时才反会true
- 两个条件类型都有相同的约束
- 两个条件类型在
true
和false
分支下都是相同的类型
两个条件类型在
true
和false
分支下都是相同的类型
理解下这句话, 从这句话能看出, type EqualTo<X, Y> = (<F>() => F extends X ? 1 : 0) extends <F>() => F extends Y ? 1 : 0
, 只有 X 与 Y 是同类型, 才能判定 F extends X ? 1 : 0
(这就是上面解释中所谓的条件类型) 与 F extends Y ? 1 : 0
是兼容的