TypeScript 是一個強大的工具,它不僅提供靜態型別檢查,還提供了許多高階功能,讓開發者能編寫更靈活、健壯的程式碼。在這篇文章中,我們將探索 5 個實用的 TypeScript 技巧,深入解釋其運作方式,幫助你更好地掌握這些進階特性。
TypeScript 中的字串字面型別不僅能表達固定的字串值,還能夠基於其他型別進行動態插值,類似於 JavaScript 中的字串模板語法。這樣的特性非常適合創建符合某種規則的字串型別,幫助你在程式設計中保持一致性。
字串字面型別插值允許你將字串與其他型別結合,創建一個動態的字串組合。例如,我們可以將 "user" 與 "Changed" 結合,生成 userChanged
。
type EventName<T extends string> = `${T}Changed`;
type UserEvent = EventName<"user">; // type UserEvent = "userChanged"
這個技術在處理事件系統時尤其有用,你可以用它來自動生成一致的事件名稱,避免手動拼接字串造成的錯誤。也可以使用在自動生成 getter 方法的名稱上。
type Getter<T extends string> = `get${Capitalize<T>}`;
type UserGetter = Getter<"username">; // type UserGetter = "getUsername"
這種字串操作不僅提升了程式碼的可讀性,還減少了代碼重複,特別是在大型專案中,能大幅減少手動操作字串的出錯率。
品牌型別(Branded Types)是 TypeScript 中的一個進階模式,它允許你在結構型別系統中引入名義型別,從而讓同樣的原始型別(例如 string
)被區分為不同的具名型別,這能防止不同概念的型別之間發生錯誤操作。
品牌型別使用交集運算符 &
與 readonly brand: unique symbol
來創建獨一無二的型別,這樣即使兩個型別底層都是 string
,也不會在 TypeScript 中被混淆。
type UserId = string & { readonly brand: unique symbol };
type PostId = string & { readonly brand: unique symbol };
function createUserId(id: string): UserId {
return id as UserId;
}
function createPostId(id: string): PostId {
return id as PostId;
}
const userId = createUserId("user123");
const postId = createPostId("post456");
// 錯誤:不能將 UserId 分配給 PostId
// const error = userId = postId;
這在需要明確區分不同類型的標識符(如 UserId
和 PostId
)時非常有用,確保它們不會被混淆使用,即使底層都是字串。在涉及 ID、代碼或任何唯一標識符的應用中,品牌型別可以顯著提高程式的安全性。
TypeScript 中的 infer
關鍵字是條件型別的一部分,它允許你從複雜的型別結構中提取型別資訊,並將其應用於後續邏輯。這在需要對函數回傳值、Promise 結果等進行型別推斷時非常有用。
在條件型別中使用 infer
可以從給定型別中推斷出內部結構,然後將其提取並使用。這種方式非常適合解包 Promise 型別或推斷函數的回傳值型別。
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
type ResolvedType = UnpackPromise<Promise<string>>; // type ResolvedType = string
type NonPromiseType = UnpackPromise<number>; // type NonPromiseType = number
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function fetchUser() { return { id: 1, name: "John" }; }
type User = ReturnType<typeof fetchUser>; // type User = { id: number; name: string; }
這種模式讓我們可以對於未知的函數或 Promise 型別進行提取和操作,尤其在動態程式設計中提供了極大的靈活性。例如,你可以用它來編寫泛型函數,根據輸入動態推斷型別,從而減少手動定義型別的麻煩。
模板字面型別允許你在型別層級上進行字串操作,類似於 JavaScript 中的模板字串,這種型別結合了字串插值和條件型別,能在型別定義上強制字串模式,這在處理字串嚴格規範(如 API 路徑、樣式名稱)時非常實用。
模板字面型別透過 type
來定義可以套用動態插值的字串,讓 TypeScript 進行靜態型別檢查。例如,你可以限制只接受特定模式的字串,防止不正確的字串格式被傳入。
type ColorVariant = "light" | "dark";
type Color = "red" | "green" | "blue";
type Theme = `${ColorVariant}-${Color}`;
function setTheme(theme: Theme) {
// 處理主題設定
}
setTheme("light-red"); // OK
// setTheme("medium-purple"); // 錯誤:參數型別不符合 'Theme'
模板字面型別非常適合 CSS-in-JS 庫、路由定義、API URL 等應用中,這些地方通常對字串有特定的命名規則,使用模板字面型別能確保符合這些規則。
遞迴型別允許型別自我引用,這對於表示樹狀結構或複雜的嵌套結構非常有用。在處理 API 回應的嵌套 JSON 或者文件結構時,遞迴型別能夠有效地描述層級結構。
遞迴型別的概念在於型別別名可以自我引用,用來表示無限深度的嵌套資料。這特別適合描述 JSON 等複雜結構。
type JSONValue =
| string
| number
| boolean
| null
| JSONValue[]
| { [key: string]: JSONValue };
const data: JSONValue = {
name: "John Doe",
age: 30,
isStudent: false,
hobbies: ["reading", "cycling"],
address: {
street: "123 Main St",
city: "Anytown",
coordinates: [40.7128, -74.0060]
}
};
這樣的型別定義能夠靈活處理 API 回應、配置文件或嵌套資料結構。當你面對深層的嵌套物件時,遞迴型別能讓型別定義簡潔、直觀且具表現力。
📌 字串字面型別插值
透過模板字面型別,可以動態創建符合特定規則的字串型別,適合建立一致的命名規則。
📌 品牌型別的使用
使用交集型別創建品牌型別,幫助避免不同實體間的混淆,增加型別安全性。
📌 infer 提取型別
利用 infer 關鍵字,可以靈活提取型別資訊,應對複雜的函數與 Promise 操作。
📌 模板字面型別
模板字面型別結合了字串型別操作,能強制字串模式,適合 API 路徑與字串處理。
📌 遞迴型別別名
遞迴型別能表示複雜的嵌套資料結構,處理樹狀資料時非常實用。
無論今天你學到了多少新的技巧,記住程式設計的旅程是一場持續不斷的成長與探索。
每一行程式碼都是一個新的開始,無論多麼複雜的問題,只要保持學習和好奇心,總能找到答案。
擁抱挑戰,未來的你會比現在更強大!加油!🚀💪