【実務・中級編】Type Aliasで実現する「判別可能な共用体(Discriminated Unions)」の高度なモデリング – TypeScript コア・型システムの基礎解析バイブル

TypeScriptの型システムを「武器」にする:判別可能な共用体による状態管理の極致

多くの開発者が `interface` と `type` を単なるデータ構造の定義として使っていますが、それはTypeScriptの真の力を半分も引き出せていません。

特に、非同期通信やUIの状態遷移を扱う際、安易な `optional` プロパティの羅列(`data?: T; error?: E; isLoading?: boolean;`)で設計していませんか? それこそが、実行時に「ありえない状態」を生み出し、バグを招く最大の要因です。

今日は、コンパイラの型推論を最大限に活用し、状態遷移を「型」として固定する「判別可能な共用体(Discriminated Unions)」の極意を伝授します。

—

なぜ「オプショナル」の羅列は悪なのか?

例えば、API通信の状態を以下のように定義したとします。

// 悪い設計の典型例
interface State {
data?: User;
error?: Error;
isLoading: boolean;
}

この定義では、`isLoading` が `false` で `data` が `undefined` のとき、アプリは「通信が終わっているのにデータがない」という論理的にありえない状態を許容してしまいます。これを確認するために、コードの至る所で `if (state.data)` といったガード節を書き散らすことになります。これは疲弊の始まりです。

—

判別可能な共用体による「状態の封じ込め」

状態を「排他的」に定義することで、コンパイラに「今、どの状態にあるか」を確実に理解させます。

// 状態を型で厳密に切り分ける
type RemoteData =
| { type: ‘IDLE’ }
| { type: ‘LOADING’ }
| { type: ‘SUCCESS’; data: T }
| { type: ‘FAILURE’; error: Error };

// 利用側の実装:状態遷移を型安全にハンドリングする
function renderUI(state: RemoteData) {
switch (state.type) {
case ‘IDLE’:
return ‘待機中…’;
case ‘LOADING’:
return ‘読み込み中…’;
case ‘SUCCESS’:
// ここで自動的に state.data にアクセス可能になる
return `データ: ${state.data}`;
case ‘FAILURE’:
// ここで自動的に state.error にアクセス可能になる
return `エラー: ${state.error.message}`;
default:
// 網羅性チェック(Exhaustive Check)
const _exhaustiveCheck: never = state;
return _exhaustiveCheck;
}
}

ここが技術の肝:

1. 排他性: `SUCCESS` 型のときには `error` プロパティは存在し得ません。IDEの補完も効き、不要なプロパティへのアクセスは型エラーになります。
2. 網羅性チェック: もし将来、新しい状態 `RETRYING` を追加した場合、`switch` 文に漏れがあれば `default` 節で `never` 型への代入エラーが発生します。コードを修正すべき場所がコンパイル時に確定するという体験は、大規模開発において最強の武器となります。

—

実務で差がつく:判別可能な共用体の「一歩先」

実務の現場では、コンポーネントのPropsやRedux/Zustandのストア設計にこれを応用します。さらに高度なテクニックとして、「識別子を抽象化する型ユーティリティ」を導入することをお勧めします。

/

  • 識別子を統一して管理するためのユーティリティ

/
type DiscriminatedUnion = T;

// 状態遷移の定義を構造化する
type UserState = DiscriminatedUnion< | { type: 'UNAUTHORIZED' } | { type: 'AUTHORIZED'; user: User; permissions: string[] } | { type: 'SUSPENDED'; reason: string } >;

このように、型定義を `type` エイリアスで集中管理することで、プロジェクト全体のドキュメントとしても機能させます。

—

パフォーマンスとコンパイル速度への配慮

「型を複雑にするとコンパイルが遅くなるのでは?」という懸念を持つ方もいるでしょう。確かに巨大な再帰的型はコンパイル負荷が高いですが、このような共用体パターンはTypeScriptコンパイラにとって非常に最適化しやすい構造です。

  • 型推論の高速化: 共用体の判別(Discrimination)は、コンパイラが最も得意とする処理の一つです。
  • メモリ効率: 実行時にはただのオブジェクトとして扱われるため、実行時のオーバーヘッドは皆無です。

—

結論:型は「守り」ではなく「攻め」の設計図

フロントエンド開発において、バグの大部分は「データの不整合」から生まれます。`if` 文による泥臭いチェックを繰り返すのではなく、「型によって不正な状態を作れないようにする」ことが、プロフェッショナルなフロントエンドエンジニアの仕事です。

明日からのコードレビューで、もし `if` や `optional` の山を見つけたら、こう言ってあげてください。

「その状態、型で封印(Discriminated)できるよ」

この設計思想を取り入れるだけで、あなたのプロダクトの堅牢性は一段上の次元へ引き上げられます。型システムを心から信頼し、コンパイラをあなたの最強のペアプログラマーにしてください。

タイトルとURLをコピーしました