【実務・中級編】 ユニオン型(Union Types)の定義と活用 – TypeScript実践ガイド

ユニオン型を「ただの繋ぎ合わせ」で終わらせるな。実戦で勝つための型絞り込み術

やあ。現場でコードを書いていて、こんな場面に出くわさないか?

「この変数は `string` かもしれないし、`number` かもしれない。とりあえず `any` にしておこうか……いや、それはエンジニアとして一番やっちゃいけないやつだぞ」

TypeScriptの中級者として一歩先へ進むには、ユニオン型(Union Types)の扱いをマスターすることが避けて通れない。単に「`|` で繋げばいい」という知識だけでは、いざ複雑なAPIレスポンスを扱うときに、型ガードの迷宮に迷い込むことになる。

今日は、現場で即戦力となるユニオン型の「絞り込み(Narrowing)」の極意を、ブラウザの挙動も含めて叩き込む。

—

1. ユニオン型とは「可能性の集合」である

TypeScriptにおいて `type T = A | B` は、単に「どちらか」という曖昧な表現ではない。集合論的に言えば、「AまたはBの要素をすべて内包する、より大きな集合」だ。

ここで注意してほしいのは、TypeScriptはコンパイル(トランスパイル)されると、型情報はすべて消滅するという点だ。ブラウザが実行するのは、型情報のないクリーンなJavaScriptだ。つまり、実行時のブラウザは「お前が今 `string` を期待しているのか `number` を期待しているのか」なんて知ったことではない。

だからこそ、我々開発者が、「実行時にどうやって型を特定するか」をコードで明示してやる必要がある。これが型ガードの正体だ。

—

2. 現場で「一番きれい」な型ガードの書き方

ユニオン型を扱う際、中途半端な `if` 文を積み重ねるとコードは腐る。現場で最も推奨される、堅牢なパターンを紹介しよう。

type Status = ‘loading’ | ‘success’ | ‘error’;

interface ApiResponse {
status: Status;
data?: string;
error?: Error;
}

function handleResponse(res: ApiResponse) {
// 1. リテラル型を活用した絞り込み
// これが最も直感的でミスが少ない
switch (res.status) {
case ‘loading’:
console.log(‘通信中です…’);
break;
case ‘success’:
// ここでは res.data が string であることがTSに伝わる
console.log(‘成功:’, res.data?.toUpperCase());
break;
case ‘error’:
// ここでは res.error が Error であることが補完される
console.error(‘失敗:’, res.error?.message);
break;
default:
// 万が一の網羅性チェック(exhaustive check)
const _exhaustiveCheck: never = res.status;
break;
}
}

なぜこの書き方が「プロ」なのか?

  • 網羅性チェック (`never`): もし後から `Status` に `’pending’` を追加したとする。すると `default` 句の `never` 代入部分でコンパイルエラーが出る。つまり、「修正漏れを未然に防げる」んだ。これは大規模開発でチームを守る最強の武器になる。

—

3. 「型ガード関数」の自作:ユーザー定義型ガード

複雑なオブジェクトが混ざったユニオン型を扱うとき、`typeof` や `instanceof` だけでは限界がくる。そんなときは、戻り値に「型述語(Type Predicates)」を持つ関数を自作する。

type User = { name: string };
type Guest = { id: number };

// ユーザー定義型ガード: 引数が特定の型であることを保証する
function isUser(person: User | Guest): person is User {
return (person as User).name !== undefined;
}

function greet(person: User | Guest) {
if (isUser(person)) {
// ここから先は person は User として扱える
console.log(`こんにちは、${person.name}さん`);
} else {
// ここでは自動的に Guest に絞り込まれる
console.log(`ゲストID: ${person.id}`);
}
}

この `person is User` という書き方。これが型ガードの魔法だ。「この関数が `true` を返したら、この変数は `User` だと断定してくれ」とコンパイラに強く命令している。

—

4. 知っておくべき「落とし穴」

最後に、現場でよくある失敗を伝授しておく。

  • `any` で逃げない: `any` を使うのは「型システムの放棄」だ。一度 `any` を使うと、その先どんなデータが流れてきてもTSは警告を出さない。それは「爆弾を抱えて走る」のと同義だ。
  • `unknown` を愛せ: 「何が来るかわからない」ときは `any` ではなく `unknown` を使う。`unknown` は「型チェックを終えるまでは何もさせないぞ」という強い意志を持つ型だ。一度絞り込めば、安全に使える。

まとめ:今日から意識すること

1. ユニオン型は「絞り込む」のが前提。
2. `switch` 文と網羅性チェックで堅牢性を担保する。
3. 複雑な条件は「型述語」を切り出して再利用性を高める。

TypeScriptの型システムは、お前たちのコードを縛り付ける鎖じゃない。未来の自分や、一緒に働く仲間がバグを生み出さないための「防護壁」だ。

この感覚を掴めれば、もう中級者の壁は突破したも同然だ。さあ、コードを書いて確かめてみてくれ。何か詰まったら、いつでも聞いてくれ。

コメント

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