Featured image of post TS 類型系統中的分支:條件類型簡介Featured image of post TS 類型系統中的分支:條件類型簡介

TS 類型系統中的分支:條件類型簡介

建立類型邏輯,根據使用條件類型約束的檢查來解析輸出結構。

介紹

TypeScript 的類型系統是圖靈完備,而釋放其全部功能的關鍵功能之一是條件類型。就像 JavaScript 中的三元運算子 (condition ? a : b) 一樣,條件類型可讓您建立類型層級邏輯:T extends U ? X : Y。這使得動態的、上下文感知的類型解析成為可能。


基本文法

條件類型檢查一個類型是否擴展另一個類型:

type IsString<T> = T extends string ? true : false;

type A = IsString<'hello'>; // true
type B = IsString<42>;      // false

條件 T extends U 詢問:「T 是否可以指派給 U?」如果是,則解析到true分支;否則,為假分支。


infer 關鍵字

infer 關鍵字可讓您從條件中提取類型:

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function greet() { return 'hello'; }
type G = ReturnType<typeof greet>; // string

infer R 宣告一個從符合位置推斷出的類型變數 R。這是許多實用程式類型的基礎。


分配條件類型

當條件類型與 union 一起使用時,它會分佈在每個成員上:

type ToArray<T> = T extends any ? T[] : never;

type Result = ToArray<string | number>;
// string[] | number[]  (not (string | number)[])

除非您將兩側包裹起來,否則這種分佈會自動發生:

type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;

type Result2 = ToArrayNonDist<string | number>;
// (string | number)[]

使用條件的內建實用程式類型

許多標準實用程式類型依賴條件類型:

// Exclude — removes types from a union
type T0 = Exclude<'a' | 'b' | 'c', 'a'>;
// 'b' | 'c'

// Extract — keeps only matching types
type T1 = Extract<string | number | (() => void), Function>;
// () => void

// NonNullable — removes null and undefined
type T2 = NonNullable<string | number | null | undefined>;
// string | number

以下是 Exclude 的實作方式:

type MyExclude<T, U> = T extends U ? never : T;

現實世界的模式

基於輸入的條件回傳類型

type AsyncResult<T> = T extends Promise<infer U> ? U : T;

async function fetchData(): Promise<string> {
  return 'data';
}

type Data = AsyncResult<ReturnType<typeof fetchData>>; // string

函數重載 無重載

type StringOrNumber<T> = T extends string ? string : number;

function process<T extends string | number>(val: T): StringOrNumber<T>;
function process(val: any): any {
  return typeof val === 'string' ? val.toUpperCase() : val * 10;
}

const a = process('hello'); // string
const b = process(42);      // number

深層條件類型範例

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? DeepReadonly<T[K]>
    : T[K];
};

interface User {
  name: string;
  address: { city: string; zip: number };
}

type ReadonlyUser = DeepReadonly<User>;
// { readonly name: string; readonly address: { readonly city: string; readonly zip: number } }

結論

條件類型是類型系統的 if/else。與 infer、分佈和映射類型相結合,它們可以實現複雜的類型轉換,從而在編譯時捕獲錯誤。掌握條件來編寫真正類型安全性、自記錄的 TypeScript 程式碼。