Featured image of post TypeScript 通用約束:建置類型安全性的 APIFeatured image of post TypeScript 通用約束:建置類型安全性的 API

TypeScript 通用約束:建置類型安全性的 API

了解如何使用 TypeScript 泛型約束(包括擴充功能、條件類型、映射類型和推斷模式)建立類型安全的 API。

TypeScript 泛型可讓您編寫可重複使用、類型安全的程式碼,但如果沒有約束,它們就太寬鬆了。 extends 關鍵字將類型參數限制為具有特定屬性的形狀,為您提供靈活性和安全性。

擴展約束

通用約束的基礎是 extends 關鍵字。它確保類型參數滿足所需的結構:

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

這裡 K extends keyof T 保證 keyobj 的有效屬性。嘗試傳遞無效金鑰會觸發編譯時錯誤。常見模式包括對介面 (T extends { id: string })、聯合成員或原始類型的約束。


條件類型

條件類型使用 extends 在編譯時檢查類型關係並選擇類型:

type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<42>;      // false

這使得類型級函數能夠根據結構轉換輸入。一個實際的例子是解包 Promise 類型:

type Unwrap<T> = T extends Promise<infer R> ? R : T;
type Result = Unwrap<Promise<number>>; // number

條件類型分佈在聯合上。 IsString<string | number> 的計算結果為 boolean (true | false),獨立地將條件套用至每個聯合成員。


使用 keyof 的映射類型

映射類型會轉換現有類型的每個屬性。與 keyof 結合使用,它們可以實現強大的物件轉換:

type Readonly<T> = { readonly [P in keyof T]: T[P] };
type Optional<T> = { [P in keyof T]?: T[P] };

TypeScript 提供了幾種基於此模式建構的內建映射類型:

類型說明使用範例
佔位符_0所有屬性可選PATCH 請求正文
佔位符_0所需的所有屬性表格提交
佔位符_0所有屬性均為唯讀不可變的配置
佔位符_0選擇鍵的子集從實體看模型
佔位符_0字典類型查找表

從 TypeScript 4.1 開始,映射類型中的 as 子句支援使用範本文字類型進行鍵重新映射:

type Getters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

推斷關鍵字

條件類型中的 infer 關鍵字從其他類型中提取類型。它為多種內建實用程式類型提供支援:

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

實際應用包括提取元件屬性類型、從第三方函式庫派生函數簽章以及解包巢狀泛型類型。 infer 位置決定提取的內容 — 傳回類型、參數元組或建構函數參數。


有約束的工廠功能

通用約束對於建立符合特定形狀的物件的工廠函數至關重要:

function createInstance<T, U extends T>(
  ctor: new (...args: any[]) => T,
  data: U
): T {
  return Object.assign(new ctor(), data);
}

此模式廣泛應用於依賴注入容器、ORM 實體工廠和 API 回應轉換器。約束 U extends T 確保資料至少包含 T 的所有屬性,同時允許附加欄位。


建置類型安全的 API 用戶端

類型安全的 API 用戶端演示了所有這些協同工作的概念:

type ApiResponse<T> =
  | { data: T; error: null }
  | { data: null; error: string };

type Endpoints = {
  "/users": User;
  "/posts": Post;
};

async function fetchApi<E extends keyof Endpoints>(
  endpoint: E
): Promise<ApiResponse<Endpoints[E]>> {
  const res = await fetch(endpoint);
  return res.json();
}

此處,條件類型從端點定義推斷回應形狀,extends 驗證路由路徑,尋找類型 Endpoints[E] 檢索正確的回應類型。


性能考慮因素

深度嵌套的泛型類型會減慢編譯速度。透過避免過度遞歸的條件類型、使用介面合併而不是複雜的映射類型以及使用輔助別名快取計算類型來緩解這一問題。使用 tsc --generateTrace 來識別瓶頸。


掌握 extends、條件類型、映射類型和 infer 可將 TypeScript 轉變為強大的類型程式語言。這些模式可以防止所有類別的執行時間錯誤,並透過編譯時保證提供卓越的開發人員體驗。