跳至主要內容
Skip to content

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">; // true

1.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>; // string

3.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<[]>; // never

4.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]>; // 3

4.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>; // User

5.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]>; // number

6.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/else
  • infer = 宣告一個「待推導」的類型變數

進階挑戰

  1. 實作 Tail<T>:取得元組除了第一個元素以外的部分
  2. 實作 Concat<T, U>:合併兩個元組
  3. 實作一個類型,提取 Promise.all 的回傳類型
typescript
// 練習 1 提示
type Tail<T extends any[]> = T extends [any, ...infer Rest] ? Rest : never;

延伸閱讀與資源