本文 AI 產出,尚未審核

TypeScript 工具型別:Exclude<T, U>

簡介

在大型前端或 Node.js 專案中,型別安全是維持程式碼品質的關鍵。TypeScript 提供了許多 工具型別(Utility Types)讓開發者可以在編譯階段快速操作型別,減少手寫冗長的型別定義。
其中 Exclude<T, U> 用來 從型別 T 中剔除 那些同時屬於 U 的成員,產生一個新的型別。這在需要根據條件過濾聯合型別(union type)時非常實用,例如排除不允許的字串、過濾 API 回傳的可選屬性等。

本文將深入說明 Exclude 的語法與運作原理,並提供多個實務範例,讓你在日常開發中能靈活運用此工具型別。


核心概念

1. 基本語法

Exclude<T, U>
  • T:欲處理的原始型別(通常是聯合型別)。
  • U:要從 T 中排除的型別集合。

Exclude<T, U> 會遍歷 T 的每個成員,若該成員 可指派給 U,則被剔除;否則保留。最終回傳的型別仍是聯合型別,只是已去除不需要的部分。

2. 為什麼不是 Pick 或手寫條件?

手寫條件往往需要使用條件型別(Conditional Types)或函式重載,程式碼會變得冗長且易出錯。Exclude 內建於 TypeScript,實作已經最佳化,且語意清晰,一眼就能看懂「我要把 X 從 Y 中排除」。

3. 與 Extract<T, U> 的關係

  • Exclude<T, U>排除 U 中的成員。
  • Extract<T, U>保留 同時屬於 TU 的成員。
    兩者互為補集,常常一起使用來做正向或反向的型別過濾。

程式碼範例

範例 1:基本字串排除

type AllColors = "red" | "green" | "blue" | "yellow";
type PrimaryColors = Exclude<AllColors, "yellow">;

// 結果: "red" | "green" | "blue"
const c: PrimaryColors = "green"; // ✅

這裡把 yellow 從所有顏色中排除,只留下主要顏色。

範例 2:排除 nullundefined(常見的非空型別)

type MaybeString = string | null | undefined;
type NonNullableString = Exclude<MaybeString, null | undefined>;

// 結果: string
function greet(msg: NonNullableString) {
  console.log("Hello, " + msg);
}
greet("World"); // ✅
// greet(null); // 編譯錯誤

Exclude 能快速得到「非空」的字串型別,等同於內建的 NonNullable<T>

範例 3:過濾 API 回傳的狀態碼

type HttpStatus = 200 | 201 | 400 | 401 | 403 | 404 | 500;
type SuccessStatus = Exclude<HttpStatus, 400 | 401 | 403 | 404 | 500>;

// 結果: 200 | 201
function isSuccess(code: HttpStatus): code is SuccessStatus {
  return code < 300;
}

只保留成功的 HTTP 狀態碼,讓後續的型別守衛(type guard)更精確。

範例 4:從介面屬性名稱中排除不允許的鍵

interface User {
  id: number;
  name: string;
  password: string;
  email?: string;
}

// 想要建立「公開資訊」型別,排除 password
type PublicKeys = Exclude<keyof User, "password">;
type PublicUser = Pick<User, PublicKeys>;

// 結果: { id: number; name: string; email?: string; }
const publicInfo: PublicUser = {
  id: 1,
  name: "Alice",
  // password: "secret", // ❌ 編譯錯誤
};

先利用 keyof 取得所有鍵,再用 Exclude 把敏感欄位剔除,最後 Pick 產生新介面。

範例 5:結合條件型別的進階使用

type Flatten<T> = T extends any[] ? T[number] : T;

// 假設有一個混合型別陣列,想要排除所有陣列元素
type Mixed = string | number[] | boolean;
type NotArray = Exclude<Mixed, any[]>;

// 結果: string | boolean
type Flat = Flatten<NotArray>; // 同樣得到 string | boolean

透過 any[] 作為排除條件,可以一次過濾掉所有陣列型別,搭配自訂條件型別可產生更彈性的工具型別。


常見陷阱與最佳實踐

陷阱 說明 建議的做法
使用 any 造成意外排除 Exclude<T, any> 會把 T 全部變成 never,因為 any 可指派給任何型別。 除非真的想得到 never,否則避免直接使用 any 作為排除條件。
忘記 U 也是聯合型別 `Exclude<1 2
never 的二次排除 Exclude<never, X> 仍是 never,不會拋錯。 若前一步已得到 never,可提前返回或使用條件型別避免不必要的運算。
與映射型別 (Mapped Types) 搭配時的可讀性 連續使用 ExcludePickOmit 會讓型別聲明變長,降低可讀性。 type alias 抽離中間結果,讓程式碼更易維護。
排除字面量型別時的遺漏 `Exclude<"a" "b"

最佳實踐

  1. 先思考「要保留什麼」再決定「排除什麼」:有時 Extract<T, U> 更直觀。
  2. 使用 type 別名分段:例如 type SensitiveKeys = "password" | "ssn",再 Exclude<keyof User, SensitiveKeys>
  3. 結合 as const:在常量陣列上使用 as const,配合 typeofExclude 可產生動態的字面量型別。
  4. 配合 lint 規則:設定 @typescript-eslint/no-unnecessary-type-arguments,避免不必要的 Exclude 呼叫。

實際應用場景

  1. 表單驗證
    • 只允許使用者輸入的欄位集合,排除系統自動產生或只讀欄位。
  2. API 版本管理
    • 舊版 API 回傳的欄位在新版本中已棄用,使用 Exclude 產生「新版本」回傳型別。
  3. 權限控管
    • 依據使用者角色排除不允許的操作代碼(enum),保證編譯期即能捕捉錯誤。
  4. 資料庫模型映射
    • 從完整的資料庫欄位型別中排除 createdAtupdatedAt,建立「建立資料」的 DTO(Data Transfer Object)。
  5. 函式重載的型別縮減
    • 在多重重載中,使用 Exclude 把不相容的參數型別剔除,讓重載簽名更精確。

總結

Exclude<T, U> 是 TypeScript 中最直觀、最常用的工具型別之一。它允許開發者 在編譯階段即剔除不需要的型別,從而得到更嚴謹且易於維護的型別定義。透過本文的說明與範例,你應該已掌握:

  • Exclude 的基本語法與運作機制。
  • 如何配合 Pickkeyof、條件型別等其他工具型別使用。
  • 常見的陷阱與避免方式。
  • 在實務專案中多種典型的應用情境。

善用 Exclude,不僅能減少手寫型別的繁瑣,還能在編譯階段捕捉更多潛在錯誤,提升程式碼的安全性與可讀性。未來面對更複雜的型別需求時,記得先思考「要保留什麼」或「要排除什麼」,選擇最合適的工具型別,讓 TypeScript 成為你最可靠的型別守護者。祝你在 TypeScript 的世界裡寫出更乾淨、更安全的程式碼!