【入門編】 判別可能なユニオン型(Discriminated Unions) – TypeScript実践ガイド

こんにちは!TypeScriptの世界へようこそ。チーフアーキテクトの私です。

TypeScriptを触り始めたばかりの頃って、型定義の多さに圧倒されたり、「なんだかエラーが厳しすぎて息苦しいな…」なんて感じたりしませんか?「`any`使っちゃえばラクなのに!」という誘惑に駆られるのも、すごくよく分かります。私も昔はそうでした。

でも、安心してください。今日お話しする「判別可能なユニオン型(Discriminated Unions)」というテクニックを知ると、TypeScriptを書くのが急に楽しく、そして劇的に安全になります。「おっ、TypeScriptって俺(私)の味方をしてくれてるんだな!」と実感できる瞬間が必ずやってきますよ。

それでは、身近な例えからゆっくり紐解いていきましょう。

—

1. カフェの注文アプリで考えてみよう

想像してみてください。あなたは今、大人気のカフェの注文管理アプリを作っています。
メニューには「コーヒー」「ケーキ」「グッズ(タンブラーなど)」があって、それぞれお客さんに提供する情報が少しずつ違います。

  • コーヒー:ホットかアイスか、サイズ(S/M/L)
  • ケーキ:店内で食べるかテイクアウトか、フォロワーの口コミ用写真が撮れるか(?)いや、アレルギー情報にしておきましょうか。小麦粉を使っているかなど。
  • グッズ:色は何色か、在庫数はいくつあるか

これを素直にTypeScriptで表現しようとすると、初心者の方が最初につまずく「巨大な一つの型(なんでも入り弁当箱)」を作りがちです。

// 😱 やりがちな「なんでも入り」の型
type MenuItem = {
name: string;
price: number;
isHot?: boolean; // コーヒー用?
size?: ‘S’ | ‘M’ | ‘L’; // コーヒー用?
hasWheat?: boolean; // ケーキ用?
color?: string; // グッズ用?
stock?: number; // グッズ用?
};

この型、一見すると動くように見えますが、実務ではバグの温床になります。なぜなら、「コーヒーなのに `hasWheat(小麦粉フラグ)` が入っている」という、現実世界ではあり得ないカオスなデータが作れてしまうからです。TypeScriptが「それ、変だよ」と教えてくれなくなっちゃうんですね。

—

2. そこで登場するのが「判別可能なユニオン型」!

ここで登場するのが、今回の主役である「判別可能なユニオン型」です。

考え方はとってもシンプル。現実の世界でも、レシートや伝票には必ず「伝票の種類(分類)」が書いてありますよね。「これはドリンクの注文です」「こっちはフードの注文です」という共通の目印(タグ)が必ずあります。

TypeScriptでも同じことをします。すべてのオブジェクトに、種類を表す共通のプロパティ(ここでは `type` と呼びましょう)を持たせるのです。

百聞は一見にしかず、実際のコードを見てみましょう。

// ☕ コーヒーの型
type Coffee = {
type: ‘coffee’; // ←これが運命の「タグ」!
name: string;
price: number;
isHot: boolean;
size: ‘S’ | ‘M’ | ‘L’;
};

// 🍰 ケーキの型
type Cake = {
type: ‘cake’; // ←これも「タグ」!
name: string;
price: number;
hasWheat: boolean;
};

// 텀블러 タンブラー(グッズ)の型
type Goods = {
type: ‘goods’; // ←これも「タグ」!
name: string;
price: number;
color: string;
stock: number;
};

// 🌟 これらを「|(パイプ)」で束ねたものが、判別可能なユニオン型!
type MenuItem = Coffee | Cake | Goods;

見てください、このスッキリとした美しさを!
それぞれの型が独立していて、「私はコーヒーです」「私はケーキです」という証明書(`type` プロパティ)を胸にぶら下げています。

—

3. タグの力で、TypeScriptが魔法のように賢くなる

さて、この `MenuItem` を使って、画面に商品の詳細を表示する関数を作ってみましょう。ここからがTypeScriptの本領発揮です。

function printItemDetails(item: MenuItem) {
// まず、共通で持っている名前と値段を表示するよ
console.log(`商品名: ${item.name}`);
console.log(`価格: ${item.price}円`);

// さあ、ここからが魔法の時間です!
// switch文で「type」をチェックしてみましょう。
switch (item.type) {
case ‘coffee’:
// ここに入った瞬間、TypeScriptは「あ、この item は確実に Coffee だな」と自動で理解します!
console.log(`サイズ: ${item.size} / ${item.isHot ? ‘ホット’ : ‘アイス’}`);
break;

case ‘cake’:
// ここに入ったら、確実に Cake です
console.log(`小麦粉使用: ${item.hasWheat ? ‘あり’ : ‘なし’}`);
break;

case ‘goods’:
// ここに入ったら、確実に Goods です
console.log(`カラー: ${item.color} / 残り在庫: ${item.stock}個`);
break;
}
}

すごくないですか!?
`switch (item.type)` で分岐しただけで、TypeScriptのコンパイラが「お、いま `case ‘cake’` の中にいるんだな。だったらこの `item` は `hasWheat` を持っているケーキに違いない!」と、自動的に型を絞り込んで(Narrowing)くれるんです。

もしここで、うっかり `case ‘coffee’` の中で `item.hasWheat` なんて書こうものなら、TypeScriptが「おいおい、コーヒーに小麦粉のプロパティなんて無いよ?」と赤く波線を引いて優しく怒ってくれます。これが実務でどれだけ心強いか、現場に出ると痛いほど分かります。

—

4. 実務でつまずきやすいポイントと、チーフからのアドバイス

ここで、初心者の頃に誰もが通る「ちょっとした罠」と、その回避法をシェアしておきますね。

罠その1: タグの名前は何でもいいの?

何でもいいです。一般的には `type` や `kind`、あるいは `tag` などと呼ばれることが多いですが、自分が分かりやすい名前で大丈夫です。ただ、チームで開発するときは「型の種類を表すプロパティ名」をある程度統一しておくと、みんながハッピーになれます。

罠その2: `switch` じゃなくて `if` でもいいの?

もちろん `if` 文でも大丈夫です!

function checkPrice(item: MenuItem) {
if (item.type === ‘coffee’) {
// このブロック内では Coffee 型として扱われます
console.log(item.size);
}
}

ただ、扱う種類が増えてきたら `switch` 文やオブジェクトのパターンマッチ的な書き方をした方が、コードの見通しが良くなるのでオススメです。

—

5. おわりに:TypeScriptはあなたを縛る敵ではなく、最高の相棒

型定義って、最初は「ルールが増えて面倒だな」と感じるかもしれません。でもそれは、あなたを縛りつけるための鎖ではなく、「未来のバグからあなたやチームのメンバーを守るための防護服」なんです。

今回紹介した「判別可能なユニオン型」は、Web制作やアプリケーション開発の現場で、APIから返ってくるJSONデータのハンドリングや、複雑なUIの状態管理(ローディング中、成功、エラーなど)で毎日のように使われる超・一軍のテクニックです。

ぜひ、あなたの手元のエディタでもコピーして、動かしてみてくださいね。「おっ、補完が効いて気持ちいい!」と思えたら、もうあなたは立派なTypeScript使いの仲間入りです。

それでは、また次のアーキテクチャでお会いしましょう。ハッピー・コーディング!

コメント

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