TypeScriptのunion(ユニオン型)を使えば、string | number のように「複数の型のどれか」を1つの変数で受け取れます。ただ、型は書けたのに使う段階でエラーが出て手が止まる、という壁に多くの人がぶつかります。

string | number みたいに「AかBのどっちか」を1つの変数で持ちたいんだけど、どう書けばいい?
ユニオン型にした変数で .toFixed() を呼んだら赤線が出た…なんでそのまま使えないんだ?
どうやって「今このときは number」って安全に確定させればいいのか分からない。

ユニオン型は「作る」だけではまだ途中で、「どの型か」を確定させる絞り込みまで理解して初めて実務で使えます。この記事では、| での書き方から、エラーになる理由、型ガードや判別可能なユニオン型で安全に絞り込むところまでを、動くコードと実際のエラー文言つきで一気につかめます。

この記事は次のような方におすすめです。

この記事はこんな人におすすめ!
  • 基本的な型注釈は書けるが、複数の型を1つの変数で扱う書き方でつまずいている方
  • ユニオン型の変数で「プロパティが存在しない」系のエラーが出て困っている方
  • 型ガードや判別可能なユニオン型での絞り込みを、実コードで身につけたい方
  • APIレスポンスの成功/失敗などを型で安全に分岐したい初〜中級者の方

読み終えるころには、ユニオン型を「作る→つまずく→絞り込む」の流れで扱えるようになり、any や強引な型アサーションに逃げずに型安全なコードが書けるようになります。

それでは、順を追って詳しく見ていきましょう!

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 の挙動は、こちらでまとめています。値をそのままの型に固定したいときに読んでみてください。
TypeScriptのas const|型が広がる罠とenum代替・注意点TypeScriptのas const(constアサーション)を基礎から解説。リテラル型への変換やwidening回避、配列・オブジェクトでの書き方、enumやsatisfies・readonlyとの使い分けと落とし穴まで整理します。...

配列・オブジェクトでのユニオン型の書き方

配列にユニオン型を使うときは、括弧の位置で意味がまったく変わるので注意が必要です。(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が valuenumber として扱ってくれるため、.toFixed() が問題なく通ります。逆に else 側では string に確定するので .toUpperCase() が使えます。

このように、条件分岐によってその範囲内でだけ型が狭まる仕組みを絞り込み(narrowing)と呼びます。TypeScriptは制御フローを解析し、「この分岐に入った時点で型はここまで限定される」と推論してくれます。

typeof は絞り込みの一手段にすぎず、値の形やクラスに応じて ininstanceof、独自の判定関数など複数の方法があります。

なかむぅ
なかむぅ
typeof・instanceof・in やユーザー定義型ガード(is)の使い分けは、型ガードの解説でまとめて扱っています。絞り込み手段を体系的に押さえたい方はどうぞ。
TypeScriptの型ガードとは?4つの判定方法と使い分けTypeScriptの型ガードとは何かを基礎から整理。typeof・instanceof・in・ユーザー定義型ガード(is)を実コードと出力で解説し、自作の落とし穴と、対象別にどれを選ぶかの判断基準まで一気にわかります。...

判別可能なユニオン型(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」のように、状態によって持つプロパティが変わるデータを型で正確に表現できるのが判別可能なユニオンの強みです。

なかむぅ
なかむぅ
成功/失敗を型で安全に扱うResult型のパターンは、こちらで解説しています。判別可能なユニオンをエラー処理設計に広げたい方に向いています。
【TypeScript】Result型でtry-catchを型安全に扱うTypeScriptのResult型を、try-catchの限界(catchがunknown・失敗が型に出ない)から自前実装、neverthrowの基本操作まで実コードで解説。導入すべき場面と「安易に入れない」判断軸も比較表で示します。...

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 を書き忘れると、defaultresloading の可能性が残るため 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 が候補になります。

なかむぅ
なかむぅ
enumの使いどころと、リテラル型ユニオンで代替する判断基準は、こちらで詳しく解説しています。どちらを選ぶか迷ったときにどうぞ。
TypeScriptのenumは非推奨?使い方と代替の判断フローTypeScriptの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 に逃げずに型安全なコードが書けます。まずは手元のコードで、ユニオン型の変数を分岐で確定させる感覚をつかんでみてください。

なかむぅ
なかむぅ
絞り込みの主役である型ガード(typeof・instanceof・is)の使い分けを、次にまとめて押さえておくと実践力が上がります。
TypeScriptの型ガードとは?4つの判定方法と使い分けTypeScriptの型ガードとは何かを基礎から整理。typeof・instanceof・in・ユーザー定義型ガード(is)を実コードと出力で解説し、自作の落とし穴と、対象別にどれを選ぶかの判断基準まで一気にわかります。...

※本記事の本文案はAIを活用して作成していますが、記載している内容およびコードは筆者が実際に調査、検証・実行し、内容の正確性を確認した上で公開しています。