# [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)