TypeScript Conditional Types 應用實例

ZiZi Zheng
萬達寵物系統發展部
4 min readDec 20, 2019

TypeScript 提供了有趣的 Conditional Types,能夠條件式的賦予 interface 欄位值的類型。本篇文章會從簡易的應用開始介紹,最終會實現以 interface 其中一個欄位的型別定義另一個欄位的資料組成

在開始往下閱讀之前,建議先具備以下幾項基本知識

  1. TypeScript 基礎
  2. 何謂泛型
  3. TypeScript Advanced Type — Conditional Types

基礎的部分,網路上有許多相關文章,可以自行挑選喜歡的研讀;此篇文章所有時做原理都包含在第 3 點,有詳細研讀的讀者應該稍微推敲就可以得出最後結論。以下 TypeScript 都會簡稱為 ts,因為字好長打得好累...

Keyword — extends

Conditional Types 使用到關鍵字 extends。在 ts 內,extends 關鍵字具有兩個含意,使用在 interface 上的話代表擴充 interface 欄位,而如果使用在欄位上則代表條件判斷

extends 使用於 interface 與 conditional types 的差異

常見用法

最常會碰見的應用是:想設計一個 interface,其中的 value 會依照傳入泛型的不同而賦予不同類型。實作方式很簡單,只需要在目標欄位上以泛型判斷後再賦予值即可

使用泛型賦予欄位不同類型

範例中創建了兩個不同型別的 product,擁有 level 欄位的 product value 型別會是 string,我們可以拿來存商品等級對應顯示的文字;而有 status 欄位的商品我們可以當作銷售用,並在 value 給予它價值

但實務上通常在寫 Product 介面時並不會考慮到所有可能使用到的介面,所以上述的例子可以改寫成

不檢查既有介面,改以檢查欄位

這個例子加入了 enum 作為第三個介面 level 的類型,可以看到 ts 對第三個介面的檢查是通過的,因為我們使用的 enum 所有值都是 string。當你把其中一個 enum 值改為非 string,或是將 Product.value extends 的 level 改為 enum 時,就能讓 ts 進退兩難(又要你給值,又不能給值~)

你搞得我
好亂 R

以欄位類型判斷

這個應用其實是我們在開發 Form 元件上遇到的實例,目的是定義每個欄位的類型,而 values 的部分可以依照前述的型別解析出對應的類型。而在底層元件是不知道

舉個例子:假設目前表單欄位有 id、price、types,我們想限制表單傳入的 values,id 只能為 string ,price 只能是 number ,types 只能是 string[]

conditional types with enum

首先,我們先定義 FormItemType做為表單元素的類型,FormProps 為表單的介面。在 FormProps 內,我們使用 keyof 擷取出型別 D 的欄位,並且以 D[K] 取出欄位的類型,接著再以 extends 判斷欄位型別符合哪個列舉,最後再著賦予指定的類型。要特別注意的是這裡的 values 是個 map,意思是指他會跑過所有傳進來的 D 的欄位名稱,並賦予它對應的型別,結果就會像是最後宣告 form 實例的模樣

這裡比較重要的是 AppFormDefine 這個介面,我們需要將各個欄位的定義以及它屬於哪種 FormItemType 預先定義好,FormProps 實際上是以 AppFormDefine 這個泛型去解析內部的資料型別。

這篇文章稍微簡化了我們的應用,有幾種再複雜一點的使用,例如

  1. FormProps 的其他欄位也可以使用同樣的判斷邏輯,並給予各種元件的介面,而不僅侷限於純量
  2. 限制 AppFormDefine 欄位型別定義,目前這個例子如果加上 test: string 不會有任何問題,但 ts 卻會要求你在 values 內宣告 test,但值又只能是 never(又是進退兩難)
  3. 如果應用上有需要,其實AppFormDefine 可以使用複合型別 |

Typescript 寫得嚴謹真的很耗費腦力,有些人可能會覺得:「阿前面不是用商品作範例嗎,怎麼後面突然就變表單了?」,這是因為筆者完全想不到要怎麼把商品的屬性切割成這麼細哈哈哈哈哈哈哈,所以只好直接把應用實例拿出來寫。若有其他心得也歡迎多多交流,讓我們成為型別與程式行數一樣多的 programmer(別開玩笑了)

--

--