union型は「AかBかどちらか」の型
まずイメージからいきます。
union型(ユニオン型)は、「この値は“これか、あれか”のどれかです」という“選択肢のある型」です。
書き方はとてもシンプルで、型と型のあいだを | でつなぎます。
let value: string | number;
value = "hello"; // OK
value = 123; // OK
value = true; // エラー(string でも number でもない)
TypeScriptこの例だと、value は「string か number のどちらか」でなければいけません。boolean を代入しようとすると、TypeScript が「その選択肢はないよ」と止めてくれます。
ここで重要なのは、「union型は“何でもOK”ではなく、“決められた候補の中からだけOK”という厳しい型」ということです。
union型の基本文法と読み方
型を「AまたはB」として宣言する
最も基本的な書き方は、次のような形です。
let id: string | number;
id = "user-1"; // OK
id = 42; // OK
TypeScript「この変数 id には、文字列か数値のどちらかが入る」という意味になります。
関数の引数にもよく使います。
function printId(id: string | number) {
console.log("ID:", id);
}
printId("user-1"); // OK
printId(42); // OK
TypeScript呼び出し側から見ると、「string でも number でも好きな方を渡していいよ」という“柔軟さ”を持った関数になります。
ただし、中で扱うときは注意が必要です。ここが次のポイントです。
union型の重要ポイント:「共通して保証されているもの」しか使えない
どちらの型にも存在するメソッドしか直接呼べない
string | number のような union型の値に対しては、両方の型に共通して存在するものしか、直接は使えません。
function printId(id: string | number) {
// console.log(id.toUpperCase()); // エラー
}
TypeScriptTypeScript が怒る理由はこうです。
「id は string かもしれないし、number かもしれない。
string なら toUpperCase があるけど、number にはない。
だから“今ここで toUpperCase を呼んでいい”とは言えない。」
つまり、union型の値をそのまま使うときは、「両方の型に共通してあるプロパティ・メソッドだけOK」というルールになります。
function printId(id: string | number) {
console.log(id.toString()); // OK(string にも number にも toString はある)
}
TypeScripttoString はどちらの型にもあるので、これはコンパイルが通ります。
ここがすごく大事なポイントで、
「ユニオン型は“幅が広いぶん、直接できることは制限される”」と理解してください。
union型と条件分岐(型ガード)をセットで考える
typeof で型を判定してから、その型として扱う
union型を「ちゃんと使える形」にするには、条件分岐で型を絞り込む(型ガード)というステップがほぼ必須です。
function printFormattedId(id: string | number) {
if (typeof id === "string") {
console.log("文字列ID:", id.toUpperCase());
} else {
console.log("数値ID:", id.toFixed(2));
}
}
TypeScriptここでは、id の型は最初 string | number ですが、
if (typeof id === "string") の中
→ id は string に絞り込まれる
else の中
→ 残りの number に絞り込まれる
というふうに、TypeScript がブロックごとに型を狭めてくれます。
この「union型+型ガード」の組み合わせが理解できると、TypeScript での型設計が急に楽しくなります。
リテラルunion型で「取りうる値」を限定する
“ON” | “OFF” のような「モード」を型にする
union型は、単に string | number のような広い型だけでなく、「特定の文字列・数値のみに絞った型」を作るのにも非常に強力です。
type SwitchState = "ON" | "OFF";
let state: SwitchState;
state = "ON"; // OK
state = "OFF"; // OK
state = "Other"; // エラー(SwitchState にその選択肢はない)
TypeScriptSwitchState は、「文字列なら何でもいい」わけではありません。
“ON” か “OFF” のどちらかだけ、という“厳密な選択肢”になっています。
関数でも同じです。
function setState(state: "ON" | "OFF") {
console.log("状態:", state);
}
setState("ON"); // OK
setState("OFF"); // OK
setState("OTHER"); // エラー
TypeScriptここで重要なのは、「自由度を下げることで、間違いを防いでいる」という設計の感覚です。
適当なスペルの “Onn” や “off” をうっかり書くと、その場でエラーになります。
「ありうる状態」を型で表現してしまうことで、バグの入り込む余地を減らせるわけです。
オブジェクトと組み合わせると「状態のバリエーション」を型にできる
もう一歩進むと、こうなります。
type LoadingState =
| { status: "idle" }
| { status: "loading" }
| { status: "success"; data: string }
| { status: "error"; error: string };
TypeScriptこれは、「読み込み処理の状態」を4パターンの union型で表現している例です。
status が “success” のときだけ data を持ち、
“error” のときだけ error を持つように設計されています。
この型を使うと、関数の中で安全に分岐できます。
function render(state: LoadingState) {
if (state.status === "idle") {
console.log("何もしていません");
} else if (state.status === "loading") {
console.log("読み込み中…");
} else if (state.status === "success") {
console.log("成功:", state.data);
} else {
console.log("失敗:", state.error);
}
}
TypeScriptstate.status === "success" のブロックでは、state が { status: "success"; data: string } 型に絞り込まれるので、state.data を安全に使えます。
ここまで来ると、
union型=「アプリの状態のバリエーションを、漏れなく・被りなく列挙したもの」
として使えるようになります。
これは TypeScript の大きな魅力です。
union型を設計するときに考えるべきこと
「本当に“どちらか”でいいのか?」を自分に問いかける
union型を使うときに一番大事なのは、「ここは“どんな値でも来ていい”場所ではなく、“この候補のどれかだけ”来ていい場所なんだ」と意識することです。
例えば次のような型があったとします。
type Id = string | number;
TypeScriptこのとき、自分に問いかけてみてください。
なぜ ID は string と number の両方で表現されうるのか?
片方に統一できない理由は何か?
呼び出し側は「どっちで渡すべきか」迷わないか?
もし、「実は全部 string にした方がシンプル」という結論なら、union にせず string に統一した方が設計としては素直です。
逆に、「サーバーからは数値、フロントでは文字列として扱うことがある」などの理由が明確にあるなら string | number も妥当です。
union型を使うときは、「どんなケースを許し、どんなケースを許さないか」の線引きを自覚的に決める
これが、型設計の質を上げる一番のポイントです。
まとめ:union型の“本質”を一言で言うと
union型は、ただの「型を並べる記号」ではありません。
「この値は、AでもいいしBでもいい。でも、それ“以外”は絶対に許さない」
という、「許される選択肢のリスト」そのものです。
だからこそ、
柔軟に受け取るべきところでは string | number のような union を使い
状態やモードを厳密にしたいところでは "ON" | "OFF" のようなリテラル union を使い
条件分岐や型ガードと組み合わせて、「どの世界ではどんなプロパティが使えるか」をはっきりさせる
という形で、「現実世界の“場合分け”をそのまま型に落とし込む道具」として活かしてほしいです。
コードを書いていて、
「ここは A のときもあれば B のときもあるな」
と思ったら、そこが union型を検討するタイミングです。
その「場合分け」を逃げずに型にしていくことが、TypeScript の一番おいしい部分につながっていきます。

