TypeScriptの基礎 - ユニオン型
概要
ユニオン型 (Union Type) とは、TypeScriptにおいて2つ以上の型を組み合わせ、そのいずれかの値を取り得ることを表現する型定義の仕組みである。
パイプ文字 | で複数の型を連結して定義し、変数や関数パラメータ、戻り値に対して複数の型を許容させることができる。
ユニオン型はTypeScriptの型システムの中核を成す機能の1つであり、any 型に頼ることなく値の不確実性を型レベルで安全に表現できる。
TypeScript 5.4以降では、クロージャ内でのユニオン型の絞り込み精度が向上し、TypeScript 5.5では配列の filter() 後の型推論も改善された。
また、TypeScript 6.0 (2026年2月リリース) ではデフォルト設定に strict: true が採用され、ユニオン型を含む型チェック全般がより厳密になっている。
|演算子によるユニオン型の宣言構文- 変数、関数パラメータ、戻り値への適用方法
- 文字列・数値リテラル型のユニオン
- 取り得る値を列挙型のように限定するパターン
- ユニオン型に対する共通メソッドの呼び出し制約
- ユニオンを構成する全ての型に存在するメソッドのみ使用可能
typeof等による型の絞り込み (Type Narrowing)- 具体的な型を識別して各型固有の操作を行う手法
- 判別可能なユニオン (Discriminated Union)
- 共通の判別子プロパティ (リテラル型) により TypeScript が型を自動識別するパターン
- 網羅性チェック (Exhaustive Check)
never型を活用して全てのケースを処理漏れなく対応する手法
string | null等の nullable な型nullやundefinedを含むユニオン型と、オプショナルチェーニングの活用
- APIレスポンスやイベントハンドラ等の実用的な型定義パターン
- 実際の開発で頻出するユニオン型の応用例
ユニオン型の宣言
基本的な構文
ユニオン型は、| 演算子で複数の型を連結して宣言する。
変数へのユニオン型の宣言例を以下に示す。
// 変数の宣言
let id: string | number;
id = "user123"; // OK
id = 42; // OK
id = true; // エラー : booleanはユニオン型に含まれていない
関数パラメータと戻り値への適用例を以下に示す。
// 関数パラメータでの使用
function printId(id: string | number) {
console.log("ID: " + id);
}
printId(101); // OK
printId("202"); // OK
// 関数の戻り値
function getUser(id: number): { name: string } | null {
if (id === 1) {
return { name: "Alice" };
}
return null;
}
配列要素の型にユニオン型を使用する場合、() でユニオン型を囲む必要がある。
// 配列要素の型定義
let values: (string | number)[];
values = ["hello", 42, "world"]; // OK
// 型エイリアスとの組み合わせ
type StringOrNumber = string | number;
type StringNumberOrBoolean = StringOrNumber | boolean;
リテラル型のユニオン
文字列・数値などのリテラル型を組み合わせることにより、取り得る値を限定したユニオン型を定義できる。
この手法は、列挙型のような用途に活用される。
- 文字列リテラル型のユニオン例
type Status = "pending" | "success" | "error"; function handleStatus(status: Status) { switch (status) { case "pending": console.log("処理中..."); break; case "success": console.log("完了しました"); break; case "error": console.log("エラーが発生しました"); break; } } handleStatus("pending"); // OK handleStatus("complete"); // エラー : "complete"はStatus型に含まれていない
- 数値リテラル型のユニオン例
type HttpStatus = 200 | 201 | 400 | 404 | 500; function respond(status: HttpStatus) { console.log("HTTPステータスコード: " + status); }
リテラル型の数が多い場合は、縦に並べる記述で可読性を高めることができる。
type Direction =
| "north"
| "south"
| "east"
| "west";
ユニオン型の操作
共通メソッドの呼び出し
ユニオン型の値に対してメソッドを呼び出す場合、ユニオンを構成する全ての型に存在するメソッドのみ呼び出すことができる。
function process(value: string | number) {
value.toString(); // OK : 全ての型に共通するメソッド
value.toUpperCase(); // エラー : numberには、toUpperCase()が存在しない
value.toFixed(2); // エラー : stringには、toFixed()が存在しない
}
下表に、string | number で使用可能なメソッド・使用不可能なメソッドの例を示す。
| 分類 | メソッド・プロパティ | 説明 |
|---|---|---|
| 使用可能 (共通) | toString() |
文字列に変換する。 |
| 使用可能 (共通) | valueOf() |
プリミティブ値を返す。 |
| stringのみ | toUpperCase() |
大文字に変換する。 |
| stringのみ | toLowerCase() |
小文字に変換する。 |
| stringのみ | charAt() |
指定インデックスの文字を返す。 |
| stringのみ | slice() |
部分文字列を返す。 |
| stringのみ | trim() |
前後の空白を除去する。 |
| stringのみ | length |
文字列の長さを返す。 |
| numberのみ | toFixed() |
小数点以下の桁数を指定して文字列化する。 |
| numberのみ | toPrecision() |
有効桁数を指定して文字列化する。 |
| numberのみ | toExponential() |
指数表記の文字列に変換する。 |
型の絞り込みによるアクセス
型固有のメソッドにアクセスするには、型の絞り込みが必要になる。
typeof 演算子等を使用して具体的な型を識別することにより、各型のメソッドを安全に呼び出せる。
function processValue(value: string | number) {
if (typeof value === "string") {
// この分岐内では、valueはstring型として扱われる
console.log(value.toUpperCase()); // OK
}
else {
// この分岐内では、valueはnumber型として扱われる
console.log(value.toFixed(2)); // OK
}
}
型の絞り込みには typeof の他にも、instanceof、in 演算子、判別可能なユニオン等の複数の手法がある。
詳細は、TypeScriptの基礎 - 型の絞り込みのページを参照すること。
判別可能なユニオン (Discriminated Union)
基本概念
判別可能なユニオン (Discriminated Union) とは、ユニオンを構成する各型が共通のリテラル型プロパティ (判別子) を持つパターンである。
TypeScript はその判別子の値によって型を自動的に絞り込むことができる。
判別可能なユニオンを構成するには、以下の条件を満たす必要がある。
- ユニオンを構成する全ての型が、同名のプロパティを持つ。
- そのプロパティの型が各型ごとに異なるリテラル型である。
- そのプロパティの値によって型を一意に識別できる。
実装パターン
判別子として kind プロパティを使用した基本的なパターンを以下に示す。
type Result =
| { kind: "success"; value: string }
| { kind: "error"; error: Error }
| { kind: "loading" };
function handleResult(result: Result) {
if (result.kind === "success") {
console.log("値:", result.value); // valueにアクセス可能
}
else if (result.kind === "error") {
console.log("エラー:", result.error.message); // errorにアクセス可能
}
else {
console.log("読み込み中...");
}
}
switch文との組み合わせは、判別可能なユニオンで最もよく使われるパターンである。
type プロパティを判別子として使用した図形の面積計算の例を以下に示す。
interface Square { type: "square"; size: number; }
interface Rectangle { type: "rectangle"; width: number; height: number; }
interface Circle { type: "circle"; radius: number; }
type Shape = Square | Rectangle | Circle;
function calculateArea(shape: Shape): number {
switch (shape.type) {
case "square":
return shape.size * shape.size;
case "rectangle":
return shape.width * shape.height;
case "circle":
return Math.PI * shape.radius * shape.radius;
}
}
上記の例では、switch文の各caseブロック内でTypeScriptが型を正確に識別するため、
各型固有のプロパティ (size、width、radius 等) に安全にアクセスできる。
網羅性チェック (Exhaustive Check)
never 型を活用することにより、ユニオン型の全てのケースを処理しているかどうかをコンパイル時に検証できる。
この手法を、網羅性チェック (Exhaustive Check) と呼ぶ。
function assertNever(x: never): never {
throw new Error("予期しない値です: " + x);
}
type Status = "pending" | "success" | "error";
function handleStatus(status: Status): void {
switch (status) {
case "pending": console.log("処理中"); break;
case "success": console.log("成功"); break;
case "error": console.log("エラー"); break;
default:
// 全てのケースを処理済みなら、statusの型はneverになる
assertNever(status);
}
}
上記の例において、Status型に"retry"を追加した場合、case "retry": が存在しないため、defaultブランチに到達する。
その時点で、statusの型は"retry"となり、never型を期待するassertNeverに渡せなくなるため、コンパイルエラーが発生する。
この仕組みにより、ユニオン型にメンバを追加した時の処理漏れをコンパイル時に検出できる。
ユニオン型の実用パターン
関数パラメータでの使用
ユニオン型を関数パラメータに使用することにより、複数の型の入力を柔軟に受け付ける関数を定義できる。
イベントハンドラの型定義の例を以下に示す。
type AppEvent =
| { type: "click"; x: number; y: number }
| { type: "keydown"; key: string; ctrlKey: boolean }
| { type: "submit"; formData: Record<string, string> };
function handleEvent(event: AppEvent) {
switch (event.type) {
case "click":
console.log(`クリック位置: (${event.x}, ${event.y})`);
break;
case "keydown":
console.log(`キー: ${event.key}, Ctrl: ${event.ctrlKey}`);
break;
case "submit":
console.log("フォームデータ:", event.formData);
break;
}
}
APIレスポンスの型定義
APIレスポンスの状態 (ローディング、成功、エラー) をユニオン型で表現することは、実際の開発でよく使用されるパターンである。
type ApiResponse<T> =
| { status: "loading" }
| { status: "success"; data: T }
| { status: "error"; error: string; code: number };
type User = { id: number; name: string };
function renderUser(response: ApiResponse<User>) {
switch (response.status) {
case "loading":
console.log("読み込み中...");
break;
case "success":
console.log(`ユーザ名: ${response.data.name}`);
break;
case "error":
console.log(`エラー (${response.code}): ${response.error}`);
break;
}
}
このパターンでは、ジェネリクス型パラメータ T を使用することにより、ユーザ、商品、投稿等の異なるデータ型に対して同じApiResponse型を再利用できる。
nullableな型
null や undefined を含むユニオン型は、値が存在しない可能性を型で表現するために使用する。
// string | null
function getUserName(id: number): string | null {
if (id === 1) return "Alice";
return null;
}
const name = getUserName(1);
// nullチェックによる型の絞り込み
if (name !== null) {
console.log(name.toUpperCase()); // OK : この分岐内で、nameはstring型
}
// Nullish Coalescing演算子 (??) によるnullのデフォルト値設定
const displayName = name ?? "匿名";
オプショナルパラメータ (?) は、自動的に undefined をユニオン型に追加する。
// valueの型は string | undefined
function printValue(value?: string) {
// オプショナルチェーニングにより、undefinedでも安全にアクセスできる
console.log(value?.toUpperCase());
}
型アサーションを使用して、nullableな型を非nullableとして扱う方法については、TypeScriptの基礎 - 型アサーションのページを参照すること。
関連情報
- TypeScriptの基礎 - tsconfig.json
- TypeScriptの基礎 - 型注釈とプリミティブ型
- TypeScriptの基礎 - 型推論
- TypeScriptの基礎 - 型エイリアス
- TypeScriptの基礎 - オブジェクト型とインターフェース
- TypeScriptの基礎 - 関数の型定義
- TypeScriptの基礎 - ユニオン型
- TypeScriptの基礎 - 型の絞り込み
- TypeScriptの基礎 - 型アサーション