TypeScriptのユニオン(Union)は「どの型か分からない」を絞り込みで解決する
TypeScriptのunion(ユニオン型)を使えば、string | number のように「複数の型のどれか」を1つの変数で受け取れます。ただ、型は書けたのに使う段階でエラーが出て手が止まる、という壁に多くの人がぶつかります。
string | number みたいに「AかBのどっちか」を1つの変数で持ちたいんだけど、どう書けばいい?
.toFixed() を呼んだら赤線が出た…なんでそのまま使えないんだ?
ユニオン型は「作る」だけではまだ途中で、「どの型か」を確定させる絞り込みまで理解して初めて実務で使えます。この記事では、| での書き方から、エラーになる理由、型ガードや判別可能なユニオン型で安全に絞り込むところまでを、動くコードと実際のエラー文言つきで一気につかめます。
この記事は次のような方におすすめです。
- 基本的な型注釈は書けるが、複数の型を1つの変数で扱う書き方でつまずいている方
- ユニオン型の変数で「プロパティが存在しない」系のエラーが出て困っている方
- 型ガードや判別可能なユニオン型での絞り込みを、実コードで身につけたい方
- APIレスポンスの成功/失敗などを型で安全に分岐したい初〜中級者の方
読み終えるころには、ユニオン型を「作る→つまずく→絞り込む」の流れで扱えるようになり、any や強引な型アサーションに逃げずに型安全なコードが書けるようになります。
それでは、順を追って詳しく見ていきましょう!
- 未経験で後悔したくない
【実体験】未経験からITエンジニアに転職して後悔した話|4社経験してわかった「最初の選択ミス」 - 年収が低くて不安
エンジニア転職体験談|4社で年収250→510万にした全記録
unionとは — 複数の型の「どれか」を1つで表す
TypeScriptのunion(ユニオン型)は、複数の型のうちどれか1つを取りうる値を表す型です。型と型を縦棒 | でつなぐだけで書けます。
type ID = string | number;
const a: ID = "abc123"; // OK:string
const b: ID = 42; // OK:number
ID は「string または number」という意味なので、文字列でも数値でも代入できます。関数の引数や戻り値にもそのまま使えます。
function toKey(id: string | number): string {
return String(id);
}
toKey("abc123"); // OK
toKey(42); // OK
ユニオン型は、Aの値の集合とBの値の集合を合わせた型として考えると理解しやすいです。この見方をすると、string 型も number 型も、それぞれ string | number という全体の部分集合(部分型)になっている、と整理できます。だからこそ string の値も number の値も ID に収まります。
型は具体的な値そのものでも構いません。"success" | "error" のように文字列リテラルを並べれば、取りうる値を特定の文字列だけに限定したリテラル型のユニオンになります。
type Status = "success" | "error";
const s: Status = "success"; // OK
const t: Status = "pending"; // 例:TS2322 Type '"pending"' is not assignable to type 'Status'.
t の行のように許可されていない文字列を入れると、その場でコンパイルエラーになります。リテラル型のユニオンは、オブジェクトから as const で固定した値をもとに組み立てる書き方とも相性がよく、設定値やステータスの表現でよく使います。
as const の挙動は、こちらでまとめています。値をそのままの型に固定したいときに読んでみてください。
配列・オブジェクトでのユニオン型の書き方
配列にユニオン型を使うときは、括弧の位置で意味がまったく変わるので注意が必要です。(string | number)[] と string[] | number[] は別物です。
// 各要素が string または number(混在OK)
const mixed: (string | number)[] = ["a", 1, "b", 2]; // OK
// 「string の配列」または「number の配列」(混在は不可)
const arr: string[] | number[] = ["a", "b"]; // OK(全部string)
const nums: string[] | number[] = [1, 2, 3]; // OK(全部number)
const bad: string[] | number[] = ["a", 1]; // 例:型が一致せずエラー
(string | number)[] は「要素1つ1つがユニオン型」なので、文字列と数値が混ざった配列を作れます。一方 string[] | number[] は「配列まるごとが string の配列か number の配列のどちらか」という意味なので、混在した ["a", 1] は弾かれます。丸括弧でユニオンを囲んでから [] を付けると要素単位のユニオンになる、と覚えておくと迷いません。
オブジェクトのプロパティにも同じ書き方でユニオン型を付けられます。
type Item = {
id: string | number;
label: string;
};
const item: Item = { id: 1, label: "sample" }; // id は number でも string でもOK
unionを作っただけでは使えない — 「どの型か分からない」エラー
ユニオン型の変数は、宣言できてもそのままでは共通でないメンバーに触れません。これがユニオン型で最初につまずきやすいポイントです。
function printNumber(value: string | number): void {
value.toFixed(2); // 例:TS2339 Property 'toFixed' does not exist on type 'string | number'.(TypeScriptバージョンにより文言が異なる場合あり)
}
.toFixed() は数値専用のメソッドで、string には存在しません。value は「string かもしれないし number かもしれない」状態なので、TypeScriptは「両方の型に共通して存在するメンバーしか安全に呼べない」と判断します。string にも number にもある .toString() なら通りますが、片方にしかない .toFixed() は拒否されるわけです。
つまりユニオン型は、中身がどちらの型か確定するまで、その型固有の機能を使わせてくれません。この「どの型か分からない」状態は、絞り込みで解消します。
unionを絞り込む — 共通でないメンバーを安全に使う
ユニオン型を安全に使う鍵は、「今この変数はどの型か」を分岐で確定させてから固有のメンバーを呼ぶことです。もっとも手軽なのは typeof を使った分岐です。
function printNumber(value: string | number): void {
if (typeof value === "number") {
console.log(value.toFixed(2)); // OK:この分岐内では value は number に確定
} else {
console.log(value.toUpperCase()); // OK:ここでは value は string に確定
}
}
if (typeof value === "number") の分岐の中では、TypeScriptが value を number として扱ってくれるため、.toFixed() が問題なく通ります。逆に else 側では string に確定するので .toUpperCase() が使えます。
このように、条件分岐によってその範囲内でだけ型が狭まる仕組みを絞り込み(narrowing)と呼びます。TypeScriptは制御フローを解析し、「この分岐に入った時点で型はここまで限定される」と推論してくれます。
typeof は絞り込みの一手段にすぎず、値の形やクラスに応じて in や instanceof、独自の判定関数など複数の方法があります。
判別可能なユニオン型(discriminated union)で分岐を安全にする
オブジェクトのユニオンを安全に分岐するには、各メンバーに共通の「識別子プロパティ(タグ)」を持たせるのが定石です。この形を判別可能なユニオン型(discriminated union)と呼びます。
type ApiResponse =
| { type: "success"; data: string }
| { type: "error"; message: string };
function handle(res: ApiResponse): void {
switch (res.type) {
case "success":
console.log(res.data); // OK:この分岐では success 側に確定
break;
case "error":
console.log(res.message); // OK:この分岐では error 側に確定
break;
}
}
共通の type プロパティが "success" | "error" というリテラル型のユニオンになっている点がポイントです。switch (res.type) で type の値を見て分岐すると、各 case の中では対応するメンバーに型が確定するため、success 側でしか存在しない data や、error 側の message に安全にアクセスできます。
APIレスポンスの「成功なら data、失敗なら message」のように、状態によって持つプロパティが変わるデータを型で正確に表現できるのが判別可能なユニオンの強みです。
neverで網羅性チェックを効かせる
判別可能なユニオンにケースを後から追加したとき、分岐の書き漏らしをコンパイラに気づかせたいことがあります。これを実現するのが never を使った網羅性チェックです。
type ApiResponse =
| { type: "success"; data: string }
| { type: "error"; message: string };
function handle(res: ApiResponse): void {
switch (res.type) {
case "success":
console.log(res.data);
break;
case "error":
console.log(res.message);
break;
default:
const exhaustive: never = res; // 全ケース処理済みなら res は never に絞られOK
throw new Error(`未対応のtype: ${exhaustive}`);
}
}
すべてのケースを処理し終えた default に到達する時点では、res に残っている候補がなくなり、型は never(値を1つも取らない型)に絞り込まれます。そのため never の変数に代入でき、コンパイルが通ります。
ここで ApiResponse に3つ目のケース、たとえば { type: "loading" } を追加し、対応する case を書き忘れると、default の res に loading の可能性が残るため never に代入できず、次のようなエラーが出ます。
// loading ケースを追加して case を書き忘れた場合
const exhaustive: never = res; // 例:TS2322 Type '{ type: "loading" }' is not assignable to type 'never'.(TypeScriptバージョンにより文言が異なる場合あり)
このエラーが「まだ処理していないケースがあるよ」という合図になり、追加漏れを実行前に検知できます。
unionと混同しやすい型の整理 — 交差型・enum・部分型
ユニオン型を学ぶと、記号や役割が似た型と混同しがちです。| のユニオン型は「どれか(OR)」、& の交差型は「すべて満たす(AND)」という点をまず押さえると整理しやすくなります。役割を1枚の表にまとめます。
| 概念 | 記号・書き方 | 意味 | 使いどころ |
|---|---|---|---|
| ユニオン型(直和型) | A | B |
AかBのどれか1つ | 複数の型のいずれかを1変数で扱う |
| 交差型(intersection) | A & B |
AとBの両方を同時に満たす | 複数の型の性質を合成する |
| enum | enum |
名前付き定数の集合 | 固定した選択肢の集合を表す |
| 部分型(部分集合) | 型どうしの関係 | ある型が別の型に含まれる関係 | 代入の可否や絞り込みの土台になる |
交差型 A & B は両方のプロパティを併せ持つ型を作るもので、「どれか」を表すユニオンとは狙いが逆です。&(すべて満たす)と |(どれか1つ)は記号も意味も対になっている、と押さえておくと混同しません。
enum は、決まった選択肢の集合に名前を付けて表す言語機能で、リテラル型のユニオンと役割が近いです。どちらも「決まった値のどれか」を表せるため、"success" | "error" のような小さな固定値の集合ならリテラル型ユニオンで足りることが多く、定数を名前でまとめたい・数値を連番で振りたいといった場面では enum が候補になります。
ここまでの整理を踏まえ、そもそも絞り込みが必要かどうかは、次のフローで判断できます。
【付録】さらに学びを深めるためのリソース
さらにTypescriptの学習を進めたい方のために、いくつかのリソースを紹介します。
これらのリソースを活用することで、TypeScriptの型システムについてより深い知識を得ることができるでしょう。
おすすめの書籍
ゼロからわかる TypeScript入門
技術評論社から出版されている「ゼロからわかる TypeScript入門」は、プログラミング初心者や本職プログラマーではない方を主な対象にした入門書です。
変数・条件分岐・ループといった基本から、クラスやインターフェース、モジュールまで段階的に学べる構成になっています。最終章ではWeb APIとJSONを使った非同期Webアプリの作成も体験できるので、「実際に動くものを作る」ところまで到達できます。
プロを目指す人のためのTypeScript入門
技術評論社の「プロを目指す人のためのTypeScript入門 安全なコードの書き方から高度な型の使い方まで」、通称 ブルーベリー本 です。
JavaScriptの仕様とTypeScript独自の機能を両方押さえつつ、リテラル型・ユニオン型・keyof型・ジェネリクスなど、高度な型表現まで踏み込んで解説しています。TypeScriptの型システムの表現力を本格的に学べる一冊です。
オンラインで参照できる公式ドキュメント
TypeScript公式ハンドブック
https://www.typescriptlang.org/docs/
TypeScriptの公式ドキュメントです。
intersection型を含む、すべての型システムの機能について詳細な説明があります。
TypeScript Deep Dive
https://basarat.gitbook.io/typescript/
TypeScriptの深い部分まで掘り下げて解説しているオンラインブックです。
無料で読むことができ、intersection型についても詳しく説明されています。
TypeScriptの学習は終わりがありません。
新しい機能が常に追加され、より良い書き方が発見されています。
継続的に学習を続けることで、より良いTypeScriptプログラマーになれるはずです。
まとめ – unionは作って、絞り込むまでが1セット
この記事の要点をまとめます。
- ユニオン型は
A | Bのように|で型をつなぎ、「複数の型のどれか」を1つの変数で表す仕組み - 配列は
(string | number)[]とstring[] | number[]で意味が変わり、丸括弧で囲むと要素単位のユニオンになる - ユニオン型はそのままでは両方に共通するメンバーしか呼べず、固有のメソッドはエラーになる
typeofなどによる絞り込み(narrowing)で分岐内の型を確定させれば、固有のメンバーを安全に使える- オブジェクトは判別可能なユニオン型にして
switchで分岐し、neverを使えばケースの追加漏れも検知できる
ユニオン型は「作る→つまずく→絞り込む」の3段を押さえれば、any に逃げずに型安全なコードが書けます。まずは手元のコードで、ユニオン型の変数を分岐で確定させる感覚をつかんでみてください。
※本記事の本文案はAIを活用して作成していますが、記載している内容およびコードは筆者が実際に調査、検証・実行し、内容の正確性を確認した上で公開しています。






