別再 if else:用 Discriminated Union 精準控制型別邏輯
本篇文章介紹 TypeScript 中的 Discriminated Union,並透過實例說明如何用它優雅地取代 if-else 判斷,讓型別幫你寫邏輯、降低錯誤風險、提升維護性。
toc
情境
小明入職後負責了一個新專案,這天他要來寫後端回傳的 API 規格,有成功或失敗兩種,成功的話會回傳
{
"status": "success",
"data": "test"
}
失敗的話則會:
{
"status": "error",
"error": "Something went wrong"
}
小明一拍腦袋,想說那就這樣定義 APIResponse
好了
type APIResponse = {
status: "success" | "error";
data?: any;
error?: any;
};
試問,上述寫法會造成什麼影響?
沒錯,經過一兩週之後,小明要開始串接 API,卻發現每次處理 API Response 都需要寫 if-else
function handleResponse(res: APIResponse) {
if (res.status === "success") {
// 處理成功邏輯
} else if (res.status === "error") {
// 處理錯誤邏輯
} else {
throw new Error("Unknown response");
}
}
你也有遇到類似的困擾嗎?當型別有兩種狀況,為求簡單將不同欄位寫成 optional
狀況,反而導致後續處理變複雜,這時候可以考慮使用 Discriminated Union
。
什麼是 Discriminated Union?
根據 TypeScript 官網 說法:
A common technique for working with unions is to have a single field which uses literal types which you can use to let TypeScript narrow down the possible current type.
簡單講就是一種特定的 Union Type,其成員包含一個可以識別的共同欄位。
可以來改寫上面的 APIResponse
,他們都有一個共同欄位叫做 status
,但成功跟失敗回傳的 status
值不同。
type Success = { status: "success"; data: any };
type Error = { status: "error"; error: any };
type APIResponse = Success | Error;
在處理邏輯時可以改寫成:
function handleResponse(res: APIResponse) {
switch (res.status) {
case "success":
// 處理成功
break;
case "error":
// 處理失敗
break;
default:
const _exhaustiveCheck: never = res;
return _exhaustiveCheck;
}
}
結語
使用 Discriminated Union
的好處是可以清楚的定義資料結構,而不會不同狀態的型別混在一起,都變成 optional
,使用 switch
來處理邏輯讓型別判斷變得更清楚。