在 TypeScript 的 Conditional Types 中加入 infer
來推斷,可以讓型別變得更加精準及泛用。
這篇文章希望能簡單的介紹一下 infer 的用處,首先必須先介紹一下什麼是 Conditional Types(條件類型)。
什麼是 Conditional Types
T extends U ? X : Y
使用 extends
關鍵字的這個看起來像是三元運算子的寫法,就是 TypeScript 的 Conditional Types。
它的目的在於創造分支,增加了型別的表達能力。這樣寫的時候,當 T
能指派給 U
,則該型別為 X
,反之則型別為 Y
。
另外,因為這個語法並不存在 if/else if/else 或是 switch/case 等寫法,所以當你有更多分支需要表達時,只能在 Conditional Types 的分支中繼續使用 Conditional Types,例如:
type TypeName<T> = T extends string
? "string"
: T extends number
? "number"
: T extends boolean
? "boolean"
: T extends undefined
? "undefined"
: T extends Function
? "function"
: "object";
infer 的使用時機
當我們希望條件符合特定的型別結構,但某個型別希望交由 TypeScript 推斷時,可以加入 infer
關鍵字來幫忙。
infer
關鍵字必須使用在「條件類型的子句」,也就是 extends
後面、?
前面的位置。
例如,我們希望推斷 Array 裡面項目的型別,就可以使用 (infer U)[]
:
type Item<T> = T extends (infer U)[] ? U : never;
這樣即可成功推斷對應的型別:
Item<string[]> // string
Item<number[]> // number
這邊值得一提的是,infer
不能寫在「型別參數的限制子句」:
// 這是錯的!
type Item<T extends (infer U)[]> = U;
因為都是使用 extends
關鍵字,我一開始也常常搞混,切記 infer
只能用在「條件類型的子句」而不能用在「型別參數的限制子句」。
infer 還可以用在什麼情況
除了推斷 Array 裡面項目的型別,infer
還可以被使用在各式各樣的狀況上。這邊介紹幾個常見的用法,例如:
- 推斷 Object 值的型別:
type ObjVal<T> = T extends { x: infer U; } ? U : never;
- 推斷 Function 參數的型別:
type FuncParamType<T> = T extends (x: infer U) => any ? U : never;
- 推斷 Function 回傳值的型別:
type FuncReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
- 推斷 Generice 參數的型別:
type PromiseType<T> = T extends Promise<infer U> ? U : never;
- 推斷 String 的一部分:
type RemoveUnderscore<T> = T extends `_${infer R}` ? R : T;
推斷 String 的部分有很多有趣的用法,我們留到下一篇文再來仔細介紹。
案例介紹
學到這邊已經差不多了,來看一個比較複雜的案例,使用的案例是 type-fest 裡面的 LastArrayElement:
這個 Type 的目的是產生 Array 最後一個項目的型別。
首先, ValueType
擁有型別參數的限制,它必須符合 readonly unknown[]
。
再來,我們可以看到第一個 Conditional Types 的條件 ValueType extends [infer ElementType]
,當 ValueType
是只有 1 個項目的 Array,則推斷裡面的東西型別為 ElementType
。
在 Array 長度不為一的情況,會遇到第二個 Conditional Types 的條件 ValueType extends [infer _, ...infer Tail]
,當 ValueType
有 2 個以上的項目,則去掉第一個項目對剩下的項目做遞迴 LastArrayElement<Tail>
。
最後,readonly unknown[]
只剩下一個狀況,長度既不為 1,也不是 2 以上,那就是 0。在這個狀況下,最後一個項目的型別為 never
。
到這邊,我們已經能順利解讀這個複雜的寫法了。