文章
TypeScript 型別守衛實戰技巧
掌握 TypeScript 型別守衛的核心概念與實用技巧,讓你的代碼更安全、更易維護。
Jay Hsu
作者
發布日期
TypeScript 的型別系統強大且靈活,但在處理聯合型別(Union Types)時,我們常需要在運行時確定具體的型別。型別守衛(Type Guards)就是解決這個問題的關鍵技術。
什麼是型別守衛?
型別守衛是一種能在特定作用域內縮小型別範圍的技術。透過型別守衛,TypeScript 編譯器能夠推斷出更精確的型別。
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function processValue(value: string | number) {
if (isString(value)) {
// 這裡 TypeScript 知道 value 是 string
console.log(value.toUpperCase());
} else {
// 這裡 TypeScript 知道 value 是 number
console.log(value.toFixed(2));
}
}
內建型別守衛
typeof 守衛
最基本的型別守衛,適用於原始型別:
function format(value: string | number) {
if (typeof value === 'string') {
return value.trim();
}
return value.toFixed(2);
}
instanceof 守衛
用於判斷類別實例:
class ApiError extends Error {
statusCode: number;
}
function handleError(error: Error | ApiError) {
if (error instanceof ApiError) {
console.log(`API Error: ${error.statusCode}`);
} else {
console.log('Generic Error');
}
}
in 守衛
檢查物件是否包含特定屬性:
interface User {
name: string;
email: string;
}
interface Admin {
name: string;
permissions: string[];
}
function greet(person: User | Admin) {
if ('permissions' in person) {
console.log(`Admin: ${person.name}`);
} else {
console.log(`User: ${person.name}`);
}
}
自定義型別守衛
使用 is 關鍵字創建自定義型別守衛:
interface Cat {
meow: () => void;
}
interface Dog {
bark: () => void;
}
function isCat(animal: Cat | Dog): animal is Cat {
return 'meow' in animal;
}
function makeSound(animal: Cat | Dog) {
if (isCat(animal)) {
animal.meow();
} else {
animal.bark();
}
}
進階技巧
斷言函數(Assertion Functions)
TypeScript 3.7+ 支援斷言函數,可以直接拋出錯誤:
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
throw new Error('Value must be a string');
}
}
function process(value: unknown) {
assertIsString(value);
// 這裡 value 被縮小為 string 型別
console.log(value.toUpperCase());
}
判別聯合(Discriminated Unions)
使用共同屬性區分不同型別:
interface Success {
status: 'success';
data: string;
}
interface Error {
status: 'error';
message: string;
}
type Response = Success | Error;
function handleResponse(response: Response) {
if (response.status === 'success') {
console.log(response.data);
} else {
console.error(response.message);
}
}
常見陷阱
1. 忘記處理所有情況
// ❌ 不完整的型別守衛
function process(value: string | number | boolean) {
if (typeof value === 'string') {
return value.toUpperCase();
}
// 忘記處理 boolean 的情況
return value.toFixed(2); // 錯誤!
}
2. 過度使用 any
// ❌ 避免使用 any
function isUser(value: any): value is User {
return value && value.name && value.email;
}
// ✅ 使用 unknown
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'name' in value &&
'email' in value
);
}
最佳實踐
- 優先使用內建型別守衛:簡單場景下,
typeof和instanceof就足夠 - 使用判別聯合:為不同型別添加
type或kind屬性 - 編寫可重用的型別守衛:將常用的型別檢查抽取為函數
- 避免過度複雜:型別守衛應該簡單明瞭
總結
型別守衛是 TypeScript 型別系統中不可或缺的一部分。掌握這些技巧能讓你的代碼更安全、更易維護。從簡單的 typeof 開始,逐步使用更進階的技術,讓 TypeScript 的型別系統為你的開發保駕護航。