# [TypeScript] Conditional Type ###### tags `TypeScript` <div style="display: flex; justify-content: flex-end"> > created: 2023/12/24 </div> ## 什麼是 Condition Type? 在撰寫程式碼時,一定會用到 `if...else` 等條件判斷,讓開發者決定在某些時刻才做某些任務。而在 TypeScript 中,當開發者在定義型別時,也可以定義條件判斷,達到在某些時刻(型別),執行某些任務(得到另一個型別),這個行為就是 Conditional Type: ```typescript= // ref. [Conditional Types](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html) interface Animal { live(): void; } interface Dog extends Animal { woof(): void; } type Example1 = Dog extends Animal ? number : string; // type Example1 = number type Example2 = RegExp extends Animal ? number : string; // type Example2 = string ``` 基本上的判斷方式就跟 JavaScript 的三元差不多,但是要注意的是,這裡是定義型別,而不是值: ```typescript= // ref. [Conditional Types](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html) SomeType extends OtherType ? TrueType : FalseType; ``` ## 接收輸入的 Generic 後,參照 Generic 判斷回傳型別: 在使用泛型別(Generic)的時候,有時候我們想要從接收的型別中拿取某些屬性的時候,就顯現出 conditional type 的好處: :::info 在 conditional type 回傳 `never` 基本上就是告知沒有這個型別 = 不該有型別(所以別用這個型別建立值)。 ::: ```typescript= type Message = { message: unknown } // 判斷接收的 T 是否包含 'message',若有則回傳 T.message 型別 type MessageOf<T> = T extends Message ? T['message']: never interface Email { message: string; // 這裡也可以變成 message: number,那麼最後的 EmailMessageContents 就是 number,因為我們在 MessageOf 已經定義若 T 有 message 時則回傳 T.message 的型別 } interface User { name: string age: number } type EmailMessageContents = MessageOf<Email>; // type EmailMessageContents = string type A = MessageOf<User> // type A = never ``` [程式範例](https://www.typescriptlang.org/play?#code/C4TwDgpgBAshDO8CGBzaBeKBvAUFKAtgsmgFxQCuAdgNZUD2A7lTgL446iSzGoQDyAMwA8AFQB8UTKKgQAHsAhUAJvB6I+UAPxRRAbQDkRDWgMBdclQgA3CACcOASyqK7gpAGNoAUQJJHADbYeIS8ZFDwwHbOKADcbE4u9u5eUACq8PbB+FRIROSR0VQoIXyWFAQARvYJnOA+foFwJhAAwvRJLmqYzSQCIr7+AeLxXNAAglLqfULCGfbiQA) 第二個範例: ```typescript= /* 判斷 T,若為 string,則回傳 type 1,若為 boolean 則回傳 type 2,若 T 本身即是 number,則回傳 T 本身 */ type ToNumber<T> = T extends string ? 1 : T extends boolean ? 2 : T type A = ToNumber<'A'> // Type A = 1 type B = ToNumber<false> // Type B = 2 type N = ToNumber<100> // Type N = 100 ``` [程式範例](https://www.typescriptlang.org/play?#code/C4TwDgpgBAKg9gOQK4FsBGEBOAeGA+KAXliggA9gIA7AEwGco7hMBLKgcygH4oBGKAFwlylWgzRw4AGwgBDKtygAmQbABQa0JCgBBIrESoMOAOQ6TeTeGgAhffGTos2AGaypdCJa3QE9w044vAAMwXhAA) 以官方文件來看: :::info `T[number]` 代表輸入泛型別陣列的型別,所以若帶入的 `T` 為 `string[]`,那麼 `T[number]` 就是 string。要注意 `T[number]` 是告訴 TypeScript 抓取陣列型別的寫法。 ::: ```typescript! // ref. [Conditional Types](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types) /* 若 Flatten 的 T 為任一陣列,則回傳該整列 element 的型別,反之則回傳輸入的 T */ type Flatten<T> = T extends any[] ? T[number] : T; type Str = Flatten<string[]>; // Type Str = string type Num = Flatten<number>; // Type Num = number ``` ### 若在條件判斷中不清楚判斷的(基準)型別為何,可以使用 `infer` 讓 TypeScript 自動推導: 以上方的例,若輸入的 T 為任一陣列型別,則回傳該陣列型別: ```typescript! type Flatten<T> = T extends any[] ? T[number] : T; ``` 我們可以使用 `infer` 關鍵字,讓 TypeScript 協助推導型別: ```typescript! /* 使用 infer 自動推導 T 陣列的型別 */ /* 所以 * 1. 這個時候我們建立一個 Item 變數 * 2. 並且使用 (infer Item)[] 達到 any[] 一樣的條件判斷 * 3. 然後直接拿 Item 達到 T[number] 取得陣列型別 */ type Flatten<T> = T extends (infer Item)[] ? Item : T; type Str = Flatten<string[]>; // type Str = string type Num = Flatten<number>; // type Num = number ``` ### 要注意喔,`infer` 是建立一個型別變數,讓 TypeScript 自動推導型別用,而 `extends` 則是確認 `A` 是否有 `B`: ```typescript! type R = { a: number } type MyType1<T> = T extends infer R ? R : never; // 這裡是拿 T infer(推導)型別 R,所以 R 會是 T 的型別 type MyType2<T> = T extends R ? R : never; // 這裡則是檢查 T 是否包含 R type T1 = MyType1<{b: string}> // T1 is { b: string; } type T2 = MyType2<{b: string}> // T2 is never type T3 = MyType3<{a: 100, b: 200}> // T3 is { a: number } ``` [程式範例](https://www.typescriptlang.org/play?#code/C4TwDgpgBASlC8UDeUCGAuKA7ArgWwCMIAnKAXwCgLRIoBZEAFXAgEYAeRgPgSkaigQAHsAhYAJgGcoASywAzErCgB+ZZiwQAbiQDcUAPQHZCpXHnEA9nj7UW9JiwBMnHon7DRE6XDVwN2nqGxgDG1mCoxND8AO4ywAAWsFQ00ay8DMyQHEgEmJLAxHIA5mQ8RnzpMtIoeVAFRVjF+pSpfE4ZjpAuufmFJWXB7bLSmjrEdrSMAMydWRA9GFCsAAwrADRQdU5rZUA) ## 參考資料 1. [Conditional Types](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types) 2. [Why is the infer keyword needed in Typescript?](https://stackoverflow.com/questions/60067100/why-is-the-infer-keyword-needed-in-typescript)