TypeScript:infer 的強大功用

C.T. Lin
4 min readAug 29, 2021

--

在 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

到這邊,我們已經能順利解讀這個複雜的寫法了。

--

--

C.T. Lin
C.T. Lin

Written by C.T. Lin

Architect @ Dcard. Author of Electron React Boilerplate and Bottender. JavaScript Developer.