こんにちは!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使いの仲間入りです。
それでは、また次のアーキテクチャでお会いしましょう。ハッピー・コーディング!

コメント