TypeScript 條件類型:infer 關鍵字
條件類型讓你根據條件選擇不同的類型。搭配 infer 可以實現強大的類型推導。
一、 條件類型基礎
1.1 基本語法
typescript
// 語法:T extends U ? X : Y
// 如果 T 符合 U,則為 X,否則為 Y
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
type C = IsString<"hello">; // true1.2 實際應用
typescript
type MessageType<T> = T extends string
? "text"
: T extends number
? "number"
: T extends boolean
? "boolean"
: "unknown";
type M1 = MessageType<string>; // 'text'
type M2 = MessageType<number>; // 'number'
type M3 = MessageType<boolean>; // 'boolean'
type M4 = MessageType<object>; // 'unknown'二、 分發條件類型
2.1 聯合類型分發
當條件類型作用於聯合類型時,會自動分發:
typescript
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>;
// string[] | number[](不是 (string | number)[])2.2 阻止分發
使用元組包裝可以阻止分發:
typescript
type ToArrayNoDistribute<T> = [T] extends [any] ? T[] : never;
type Result = ToArrayNoDistribute<string | number>;
// (string | number)[]2.3 過濾類型
typescript
// 從聯合類型中過濾掉 null 和 undefined
type NonNullable<T> = T extends null | undefined ? never : T;
type A = NonNullable<string | null | undefined>;
// string
type B = NonNullable<number | null>;
// number三、 infer 關鍵字
3.1 基本用法
infer 用於在條件類型中推導類型:
typescript
// 推導函式回傳類型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function greet(): string {
return "hello";
}
type Result = ReturnType<typeof greet>; // string3.2 推導參數類型
typescript
// 推導函式參數類型
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
function add(a: number, b: number): number {
return a + b;
}
type Params = Parameters<typeof add>; // [number, number]3.3 推導陣列元素
typescript
// 推導陣列元素類型
type ElementType<T> = T extends (infer E)[] ? E : never;
type A = ElementType<string[]>; // string
type B = ElementType<number[]>; // number
type C = ElementType<(string | number)[]>; // string | number四、 進階 infer 模式
4.1 推導 Promise 內部類型
typescript
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<number>>>; // number(遞迴解包)4.2 推導元組第一個元素
typescript
type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;
type A = First<[string, number, boolean]>; // string
type B = First<[1, 2, 3]>; // 1
type C = First<[]>; // never4.3 推導元組最後一個元素
typescript
type Last<T extends any[]> = T extends [...any[], infer L] ? L : never;
type A = Last<[string, number, boolean]>; // boolean
type B = Last<[1, 2, 3]>; // 34.4 推導字串
typescript
// 移除字串開頭空白
type TrimStart<S extends string> = S extends ` ${infer Rest}`
? TrimStart<Rest>
: S;
type A = TrimStart<" hello">; // 'hello'
type B = TrimStart<"world">; // 'world'五、 實用工具類型
5.1 實作 ReturnType
typescript
type MyReturnType<T extends (...args: any[]) => any> = T extends (
...args: any[]
) => infer R
? R
: never;5.2 實作 InstanceType
typescript
type MyInstanceType<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: any) => infer R ? R : never;
class User {
name: string = "";
}
type UserInstance = MyInstanceType<typeof User>; // User5.3 實作 ConstructorParameters
typescript
type MyConstructorParameters<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: infer P) => any ? P : never;
class User {
constructor(name: string, age: number) {}
}
type Params = MyConstructorParameters<typeof User>; // [string, number]5.4 提取函式第一個參數
typescript
type FirstArg<T extends (...args: any[]) => any> = T extends (
first: infer F,
...rest: any[]
) => any
? F
: never;
function greet(name: string, age: number): void {}
type First = FirstArg<typeof greet>; // string六、 條件類型與約束
6.1 使用約束
typescript
type GetLength<T> = T extends { length: infer L extends number } ? L : never;
type A = GetLength<"hello">; // number
type B = GetLength<[1, 2, 3]>; // number6.2 遞迴條件類型
typescript
type Flatten<T> = T extends any[] ? Flatten<T[number]> : T;
type A = Flatten<[[string]]>; // string
type B = Flatten<[[[number]]]>; // number總結
| 語法 | 說明 | 範例 |
|---|---|---|
T extends U ? X : Y | 條件類型 | 根據條件選擇類型 |
infer R | 類型推導 | 在條件中推導類型 |
| 分發 | 聯合類型拆開處理 | 自動行為 |
[T] | 阻止分發 | 包裝成元組 |
> **記憶技巧**:
extends= if/elseinfer= 宣告一個「待推導」的類型變數
進階挑戰
- 實作
Tail<T>:取得元組除了第一個元素以外的部分 - 實作
Concat<T, U>:合併兩個元組 - 實作一個類型,提取 Promise.all 的回傳類型
typescript
// 練習 1 提示
type Tail<T extends any[]> = T extends [any, ...infer Rest] ? Rest : never;