題目
中文
實現類型的 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
是兼容的