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>:保留 同時屬於T與U的成員。
兩者互為補集,常常一起使用來做正向或反向的型別過濾。
程式碼範例
範例 1:基本字串排除
type AllColors = "red" | "green" | "blue" | "yellow";
type PrimaryColors = Exclude<AllColors, "yellow">;
// 結果: "red" | "green" | "blue"
const c: PrimaryColors = "green"; // ✅
這裡把
yellow從所有顏色中排除,只留下主要顏色。
範例 2:排除 null 與 undefined(常見的非空型別)
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) 搭配時的可讀性 |
連續使用 Exclude、Pick、Omit 會讓型別聲明變長,降低可讀性。 |
以 type alias 抽離中間結果,讓程式碼更易維護。 |
| 排除字面量型別時的遺漏 | `Exclude<"a" | "b" |
最佳實踐
- 先思考「要保留什麼」再決定「排除什麼」:有時
Extract<T, U>更直觀。 - 使用
type別名分段:例如type SensitiveKeys = "password" | "ssn",再Exclude<keyof User, SensitiveKeys>。 - 結合
as const:在常量陣列上使用as const,配合typeof與Exclude可產生動態的字面量型別。 - 配合 lint 規則:設定
@typescript-eslint/no-unnecessary-type-arguments,避免不必要的Exclude呼叫。
實際應用場景
- 表單驗證
- 只允許使用者輸入的欄位集合,排除系統自動產生或只讀欄位。
- API 版本管理
- 舊版 API 回傳的欄位在新版本中已棄用,使用
Exclude產生「新版本」回傳型別。
- 舊版 API 回傳的欄位在新版本中已棄用,使用
- 權限控管
- 依據使用者角色排除不允許的操作代碼(enum),保證編譯期即能捕捉錯誤。
- 資料庫模型映射
- 從完整的資料庫欄位型別中排除
createdAt、updatedAt,建立「建立資料」的 DTO(Data Transfer Object)。
- 從完整的資料庫欄位型別中排除
- 函式重載的型別縮減
- 在多重重載中,使用
Exclude把不相容的參數型別剔除,讓重載簽名更精確。
- 在多重重載中,使用
總結
Exclude<T, U> 是 TypeScript 中最直觀、最常用的工具型別之一。它允許開發者 在編譯階段即剔除不需要的型別,從而得到更嚴謹且易於維護的型別定義。透過本文的說明與範例,你應該已掌握:
Exclude的基本語法與運作機制。- 如何配合
Pick、keyof、條件型別等其他工具型別使用。 - 常見的陷阱與避免方式。
- 在實務專案中多種典型的應用情境。
善用 Exclude,不僅能減少手寫型別的繁瑣,還能在編譯階段捕捉更多潛在錯誤,提升程式碼的安全性與可讀性。未來面對更複雜的型別需求時,記得先思考「要保留什麼」或「要排除什麼」,選擇最合適的工具型別,讓 TypeScript 成為你最可靠的型別守護者。祝你在 TypeScript 的世界裡寫出更乾淨、更安全的程式碼!