TypeScript 型別守衛 (Type Guards)
TypeScript 的編譯器非常聰明,但在某些情況下,它無法自動判斷一個變數的確切型別,這時候我們就需要「型別守衛 (Type Guards)」。
簡單來說,型別守衛就是一個回傳 boolean 的函式,但它多了一個特殊的功用:告訴 TypeScript 編譯器「如果這個函式回傳 true,那這個變數就是某某型別」。
為什麼需要型別守衛?
假設我們有兩種動物:
interface Fish {
swim(): void;
}
interface Bird {
fly(): void;
}
function move(animal: Fish | Bird) {
// 這裡 TypeScript 只知道 animal 可能是 Fish 或 Bird
// 所以你不能直接呼叫 swim() 或 fly(),因為不確定它到底是哪一種
// animal.swim(); // 錯誤!Bird 不會游泳
}
我們可以用 instanceof 或 in 來檢查,但如果邏輯比較複雜,我們通常會把它封裝成一個函式。
但問題來了:TypeScript 編譯器不會去分析你函式裡面的邏輯。
// 普通的檢查函式
function isFish(animal: Fish | Bird): boolean {
return (animal as Fish).swim !== undefined;
}
// 即使 isFish 回傳 true,TypeScript 還是不知道 animal 是 Fish
if (isFish(animal)) {
// animal.swim(); // 還是錯誤!
}
這就是為什麼我們需要特殊的語法:Type Predicate (型別謂詞)。
自訂型別守衛 (parameter is Type)
我們只需要把回傳型別從 boolean 改成 arg is Type:
// 關鍵在於回傳值:animal is Fish
function isFish(animal: Fish | Bird): animal is Fish {
return (animal as Fish).swim !== undefined;
}
if (isFish(animal)) {
// 這裡 animal 被正確收窄為 Fish
animal.swim(); // OK!
} else {
// 這裡 TypeScript 很聰明地推論出,如果不適 Fish,那一定是 Bird
animal.fly(); // OK!
}
語法重點
function isString(value: unknown): value is string {
return typeof value === 'string';
}
- 函式必須回傳
boolean。 - 回傳型別必須寫成
參數名 is 型別。 - 這個語法只能用在回傳 boolean 的函式上。
進階:斷言函式 (Assertion Functions)
有時候我們不想要回傳 boolean,而是希望「如果不符合型別就直接丟出錯誤 (Throw Error)」。這在寫測試或驗證 API 資料時很常用。
TypeScript 3.7 推出了 asserts 關鍵字。
// 注意:asserts value is string
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new Error('Value must be a string');
}
}
function process(input: unknown) {
assertIsString(input);
// 程式能執行到這行,代表沒有丟出錯誤
// 所以 input 一定是 string
console.log(input.toUpperCase()); // OK
}
實用範例:過濾陣列中的 Null
這是一個非常經典的案例。假設我們有一個混雜 null 的陣列:
const items = ['a', null, 'b', undefined, 'c'];
// items 型別是 (string | null | undefined)[]
// 我們想過濾掉 null 和 undefined
// 錯誤做法:
const strings = items.filter((item) => item != null);
// strings 的型別其實還是 (string | null | undefined)[]
// 因為 Array.prototype.filter 不會自動變更型別
正確做法是搭配型別守衛:
// 定義一個守衛
function isNotNull<T>(value: T | null | undefined): value is T {
return value != null;
}
const strings = items.filter(isNotNull);
// 現在 strings 被正確推論為 string[]
總結
型別守衛是連接「執行時期邏輯」與「編譯時期型別」的橋樑。
- 當 TypeScript 無法自動判斷型別時,寫一個型別守衛函式。
- 使用
arg is Type語法告訴編譯器判斷結果。 - 使用
asserts arg is Type來處理拋出錯誤的驗證邏輯。