型は「ドキュメント」ではなく「契約」である:Conditional TypesによるAPIレスポンスの静的安全性
TypeScriptを単なる「JavaScriptに型を付ける道具」だと思っているなら、今すぐその認識を改めるべきだ。コンパイラにとって、型定義とは実行時の挙動を制限する「契約(Contract)」そのものだ。
特に、外部APIと対峙するフロントエンドにおいて、レスポンスの構造がフラグひとつで変わるような「不整合なAPI」を扱うとき、多くのエンジニアは安易に `any` を使ったり、巨大でスカスカな `interface` を作ってごまかす。だが、それは技術的な敗北だ。
今回は、Conditional Types(条件付き型)を駆使し、APIのレスポンス形状を型レベルで完全に掌握する、実務直結の設計パターンを伝授する。
—
1. なぜ「雑な型定義」がバグを生むのか
例えば、以下のようなAPIを考えてみよう。`status` フラグによって `data` の中身が変わる典型的なパターンだ。
// よくある「ダメな」型定義
interface ApiResponse {
status: ‘success’ | ‘error’;
data?: any; // ここでanyを使うのは思考停止の証拠
message?: string;
}
この定義では、`status === ‘success’` であることをコード側でチェックしても、`data` が存在するかどうかをTSは保証できない。結果として、開発者は不要な `if` 文や `as` キャストを連発することになる。これは保守性の崩壊を招く。
—
2. 解決策:Conditional Typesによる「判別可能な共用体」の動的生成
Conditional Typesは、型計算機としてのTypeScriptの真骨頂だ。これを使えば、APIの構造を「型の関係性」として定義できる。
実装例:型安全なレスポンスハンドラ
// 成功時と失敗時の構造を明確に分離する
type SuccessResponse
status: ‘success’;
data: T;
};
type ErrorResponse = {
status: ‘error’;
message: string;
};
// これがConditional Typesの神髄
// Tがどのステータスかによって型を自動的に絞り込む
type ApiResponse
? SuccessResponse
: ErrorResponse;
// 使用例:APIクライアントの型
async function fetchUser(
status: S
): Promise
// 実際の実装は省略。戻り値の型がstatusに応じて動的に解決される
return {} as any;
}
// 現場での活用
const res = await fetchUser(‘success’);
if (res.status === ‘success’) {
// ここでTSはres.dataへのアクセスを完全に許可する
console.log(res.data.name);
} else {
// ここではres.messageが存在することが保証されている
console.error(res.message);
}
この設計の凄み
- 推論の連鎖: `status` をチェックした瞬間に、TSは `ApiResponse` の型引数を解決し、適切なフィールド以外を「存在しないもの」として扱う。
- 堅牢性: APIの仕様変更があっても、型定義を直せば利用箇所すべてでコンパイルエラーとして検知できる。
—
3. パフォーマンスとコンパイル速度への配慮
「型が複雑になるとコンパイルが遅くなるのではないか?」という質問をよく受ける。結論から言うと、今回のようなConditional Typesは非常に軽量だ。
しかし、「あまりに深い条件分岐」や「再帰的な型生成」は禁忌だ。特に `infer` キーワードを多用した複雑な型メタプログラミングは、IDEの補完(Language Service)を重くする。
- ルール: 型定義は「可読性」と「厳密さ」のバランスを優先せよ。
- ヒント: 複雑すぎる型は、一度 `type` エイリアスとして名前を付け、名前付きでエクスポートする。これにより、TSの型キャッシュが効きやすくなる。
—
4. チーフアーキテクトからの提言
多くのエンジニアが陥る罠は、「APIが汚いから、型も汚くていい」という妥協だ。
だが、フロントエンドの役割は、「外部の不整合を、アプリケーション内部の整合性に変換すること」にある。
APIのレスポンスをそのままコンポーネントに流し込むのではなく、今回紹介したようなConditional Typesを使って「型安全な境界」を作れ。これだけで、`undefined` のプロパティにアクセスして画面をホワイトアウトさせるような、低レベルな事故を100%防ぐことができる。
型システムは、お前たちの書くコードを縛る鎖ではない。
不確実な世界(実行時)から、安全な領域(コンパイル時)へと守ってくれる、最強の盾なのだ。
今日から `any` を排除し、型による厳密な契約を結べ。それが、一流のエンジニアへの第一歩だ。