はじめに
この記事では、タグ付き(判別可能な)ユニオン型 (discriminated union) の概要を記載します。
通常のユニオン型
タグ付きユニオン型の説明の前に通常のユニオン型について、説明します。
ユニオン型では、型をパイプ (|
) で繋げて表現します。
以下の AuthState
では、status
は必須、user
と message
はオプショナルになっています。
sample.ts
type AuthState =
| { status: string; user?: { id: string; name: string }; message?: string };
AuthState
型を利用して status
に応じたメッセージを表示する関数を作成したい場合、オプショナルのプロパティの存在をチェックする条件文を書く必要があります。
sample.ts
function displayAuthMessage(state: AuthState): string {
if (state.status === "unauthenticated") {
return "You are not logged in.";
}
if (state.status === "authenticating") {
return "Logging in... Please wait.";
}
if (state.status === "authenticated") {
if (state.user) {
return `Welcome back, ${state.user.name}!`;
}
return "Error: Missing user information.";
}
if (state.status === "error") {
if (state.message) {
return `Authentication failed: ${state.message}`;
}
return "Error: An unknown error occurred.";
}
// No exhaustive type checking
return "Unknown state.";
}
ユースケースとして、status
が "authenticated"
の時は必ず user
が存在するとした場合、この分岐は冗長です。タグ付きユニオン型を使えば、この冗長性を解消できます。
タグ付きユニオン型
タグ付きユニオン型では、ユニオンに属するの各オブジェクトの型を区別するための「タグ」がつきます。
以下の AuthState
では、status
が「タグ」に該当します。
sample.ts
type AuthState =
| { status: "unauthenticated" }
| { status: "authenticating" }
| { status: "authenticated"; user: { id: string; name: string } }
| { status: "error"; message: string };
通常のユニオン型とは異なり、user
と message
の存在チェックが不要になります。その結果、分岐処理が読みやすく、保守性も高くなります。
sample.ts
function displayAuthMessage(state: AuthState): string {
switch (state.status) {
case "unauthenticated":
return "You are not logged in.";
case "authenticating":
return "Logging in... Please wait.";
case "authenticated":
return `Welcome back, ${state.user.name}!`;
case "error":
return `Authentication failed: ${state.message}`;
default: {
// Exhaustiveness check
const _exhaustive: never = state;
throw new Error(`Unhandled state: ${_exhaustive}`);
}
}
}