
TypeScriptの実践的な型
union型・型ガード・型推論を解説
TypeScriptの実践的な型を解説。union型・literal型・型ガード・型推論の使い方を初心者向けにわかりやすく紹介します。
全8回シリーズ — 目次
1 TypeScriptとは
2 環境構築
3 基本の型
4 関数と型
5 インターフェイス
6 クラス
7 実践的な型
8 便利機能まとめ
🎯 この記事のゴール
Union型・Literal型・型ガード・型推論を組み合わせて、「TypeScriptらしい書き方」ができるようになること。この回を理解すると、コードの安全性と表現力が一気に上がります。 実行ファイルを用意しよう
今回用のファイルを作成します。Bunの場合はadvanced.ts 、Node.jsの場合はsrc/advanced.ts です。
Bunの場合
touch advanced.ts
bun --watch run advanced.ts Node.js + npm
touch src/advanced.ts
npx tsc --watch # 別ターミナルで起動
node dist/advanced.js Union型 — 複数の型を「または」でまとめる
Union型は| (パイプ)で型を並べて「AまたはBまたはC」を表現します。最も使う頻度が高いTypeScript機能のひとつです。
string
|
number
|
boolean
=
どれかひとつの型を取れる
// ① 変数に Union 型を使う
let id: number | string;
id = 1; // OK
id = "abc-123"; // OK
id = true; // エラー: boolean は number | string に代入不可
// ② 関数の引数に Union 型を使う
function printId(id: number | string): void {
console.log(`ID: ${id}`);
}
printId(42); // → ID: 42
printId("abc"); // → ID: abc
// ③ null / undefined との Union(実務で頻出)
function findUser(id: number): { name: string } | null {
if (id === 1) return { name: "Alice" };
return null; // 見つからなかった場合
}
const user = findUser(1);
if (user) {
console.log(user.name); // → Alice(nullチェック後は安全にアクセスできる)
} Bunの場合
bun run advanced.ts
#ID: 42
#ID: abc
#Alice Node.js + npm
npx tsc && node dist/advanced.js
#ID: 42
#ID: abc
#Alice Literal型 — 値そのものを型にする
Literal型は"draft" や0 のように 特定の値だけを許可する型 です。Union型と組み合わせることで、取り得る値を厳密に限定できます。APIのステータスコードやイベント名の定義に非常に有用です。
// 文字列 Literal 型
type Direction = "north" | "south" | "east" | "west";
function move(dir: Direction): void {
console.log(`${dir}に移動`);
}
move("north"); // OK
move("up"); // エラー: "up" は Direction 型に存在しない
// 数値 Literal 型
type DiceValue = 1 | 2 | 3 | 4 | 5 | 6;
function rollDice(): DiceValue {
return (Math.floor(Math.random() * 6) + 1) as DiceValue;
}
// 実務でよく使うパターン:ステータス管理
type OrderStatus =
| "pending"
| "confirmed"
| "shipping"
| "delivered"
| "cancelled";
interface Order {
id: number;
status: OrderStatus;
total: number;
}
function getStatusLabel(status: OrderStatus): string {
const labels: Record<OrderStatus, string> = {
pending: "注文受付",
confirmed: "注文確定",
shipping: "配送中",
delivered: "配送完了",
cancelled: "キャンセル",
};
return labels[status];
}
console.log(getStatusLabel("shipping")); // → 配送中
console.log(rollDice()); // → 1〜6 のどれか Bunの場合
bun run advanced.ts
#配送中
#4 Node.js + npm
npx tsc && node dist/advanced.js
#配送中
#4✨ as const — 配列・オブジェクトをリテラル型として固定する
as const を付けると、配列やオブジェクトのすべての値がリテラル型として推論されます。定数を型の元ネタにしたいときに便利です。const ROLES = ["admin", "editor", "viewer"] as const;
// ROLES の型は readonly ["admin", "editor", "viewer"]
// 配列の要素型を取り出す
type Role = typeof ROLES[number]; // "admin" | "editor" | "viewer"
function assignRole(role: Role): void {
console.log(`ロール「${role}」を割り当てました`);
}
assignRole("admin"); // OK
assignRole("owner"); // エラー: "owner" は Role に存在しない 型ガード — 実行時に型を絞り込む
Union型の変数を使う際、TypeScriptは「どちらの型かわからない」状態では型固有のメソッドを使わせてくれません。 型ガード は「この時点では確実にこの型だ」と TypeScript に伝えるための仕組みです。
| TYPEOF | プリミティブ型(string / number / boolean)の絞り込みに使う。最も基本的な型ガード。 |
|---|---|
| INSTANCEOF | クラスのインスタンスかどうかを判定する。Date / Error / 自作クラスなどに使う。 |
| IN 演算子 | オブジェクトに特定のプロパティが存在するかで判定する。interface の判別に使う。 |
| 型述語関数 | is キーワードで自作の型ガード関数を定義する。複雑な条件でも再利用できる。 |
① typeof による型ガード
function formatValue(value: string | number): string {
if (typeof value === "number") {
// このブロック内では value は number として扱われる
return value.toLocaleString(); // → "1,234"
}
// ここでは value は string に絞り込まれる
return value.trim(); // → 前後の空白を除去
}
console.log(formatValue(1234)); // → 1,234
console.log(formatValue(" hello ")); // → hello ② in 演算子による型ガード
interface Cat { meow: () => void }
interface Dog { bark: () => void }
type Animal = Cat | Dog;
function makeSound(animal: Animal): void {
if ("meow" in animal) {
// ここでは animal は Cat に絞り込まれる
animal.meow();
} else {
// ここでは animal は Dog に絞り込まれる
animal.bark();
}
}
const cat: Cat = { meow: () => console.log("ニャー") };
const dog: Dog = { bark: () => console.log("ワン") };
makeSound(cat); // → ニャー
makeSound(dog); // → ワン ③ 型述語関数(is キーワード)
複雑な判定ロジックを関数に切り出して再利用できます。戻り値の型に引数名 is 型 を書くのがポイントです。
interface ApiSuccess {
status: "success";
data: unknown;
}
interface ApiError {
status: "error";
message: string;
}
type ApiResponse = ApiSuccess | ApiError;
// 型述語関数:引数 is 型 という戻り値型を書く
function isSuccess(res: ApiResponse): res is ApiSuccess {
return res.status === "success";
}
function handleResponse(res: ApiResponse): void {
if (isSuccess(res)) {
// res は ApiSuccess として扱われる
console.log("成功:", res.data);
} else {
// res は ApiError として扱われる
console.log("エラー:", res.message);
}
}
handleResponse({ status: "success", data: { id: 1 } }); // → 成功: { id: 1 }
handleResponse({ status: "error", message: "Not Found" }); // → エラー: Not Found Bunの場合
bun run advanced.ts
#ニャー
#ワン
#成功: { id: 1 }
#エラー: Not Found Node.js + npm
npx tsc && node dist/advanced.js
#ニャー
#ワン
#成功: { id: 1 }
#エラー: Not Found 判別可能なUnion(Discriminated Union)
共通の タグ(判別子) プロパティを持つ Union 型を使うパターンです。switch 文と組み合わせると型が自動的に絞り込まれ、 すべてのケースを網羅しているか を TypeScript が保証してくれます。
// "kind" が判別子(タグ)の役割を担う
type Shape =
| { kind: "circle"; radius: number }
| { kind: "rectangle"; width: number; height: number }
| { kind: "triangle"; base: number; height: number };
function calcArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
// shape は { kind: "circle"; radius: number } に絞り込まれる
return Math.PI * shape.radius ** 2;
case "rectangle":
return shape.width * shape.height;
case "triangle":
return (shape.base * shape.height) / 2;
default:
// ここに到達したら never 型になる → 網羅チェック
const _exhaustive: never = shape;
return _exhaustive;
}
}
console.log(calcArea({ kind: "circle", radius: 5 }).toFixed(2)); // → 78.54
console.log(calcArea({ kind: "rectangle", width: 4, height: 6 })); // → 24
console.log(calcArea({ kind: "triangle", base: 3, height: 8 })); // → 12 Bunの場合
bun run advanced.ts
#78.54
#24
#12 Node.js + npm
npx tsc && node dist/advanced.js
#78.54
#24
#12💡 never による網羅チェックの仕組み
default ブランチで変数にnever 型を代入しようとすると、もし処理が到達した場合はコンパイルエラーになります。新しいkind を追加してcase に書き忘れたとき、TypeScript が「対応が漏れている」と教えてくれます。 型推論 — TypeScript が型を自動で読み取る
TypeScript は多くの場面で型を自動推論します。すべての変数に型注釈を書く必要はなく、 「書いたほうが読みやすいとき」だけ書く のが実務の正解です。
変数の初期化
const x = 42 → number
const s = "hello" → string
初期値から型が明らかな場合は省略OK。
関数の戻り値
return の値から戻り値の型を推論。シンプルな関数は省略できるが、公開APIには明示推奨。
配列・オブジェクト
[1, 2, 3] → number[]
{ a: 1 } → { a: number }
要素・プロパティの型から推論。
制御フロー後
if・switch・typeof の後で型が自動的に絞り込まれる。これが型ガードの仕組み。
// ① 変数の推論(型注釈は不要)
const count = 0; // number と推論
const name = "Alice"; // string と推論
const flags = [true, false]; // boolean[] と推論
// ② 戻り値の推論
function double(n: number) { // 戻り値は number と推論される
return n * 2;
}
// ③ 制御フローによる絞り込み(型ガードなしで自動絞り込み)
function processInput(input: string | number | null): string {
if (input === null) {
return "入力なし"; // input は null に絞り込まれる
}
if (typeof input === "number") {
return input.toString(); // input は number に絞り込まれる
}
return input.toUpperCase(); // input は string に絞り込まれる
}
console.log(processInput(null)); // → 入力なし
console.log(processInput(42)); // → 42
console.log(processInput("hello")); // → HELLO
// ④ ReturnType — 関数の戻り値型を取り出す
type DoubleResult = ReturnType<typeof double>; // number
const result: DoubleResult = double(21);
console.log(result); // → 42 Bunの場合
bun run advanced.ts
#入力なし
#42
#HELLO
#42 Node.js + npm
npx tsc && node dist/advanced.js
#入力なし
#42
#HELLO
#42 第7回 早見表
| 機能 | 書き方 | 主な用途 |
|---|---|---|
| Union型 | A | B | C | 複数の型を取り得る変数・引数 |
| Literal型 | "draft" | "published" | 取り得る値を限定したい場面(ステータス・方向など) |
| as const | ["a","b"] as const | 配列・オブジェクトをリテラル型として固定する |
| typeof ガード | typeof x === "string" | プリミティブ型の絞り込み |
| in ガード | "prop" in obj | interface の判別 |
| 型述語関数 | x is SomeType | 再利用可能なカスタム型ガード |
| Discriminated Union | 共通タグ + switch | ケース網羅チェック付きの型分岐 |
| 型推論 | 型注釈を省略 | 初期値・戻り値・制御フロー後の自動絞り込み |
| ReturnType | ReturnType<typeof fn> | 関数の戻り値型を取り出す |
📌 第7回のまとめ
Union型・Literal型・型ガード・Discriminated Union・型推論を学びました。特に Discriminated Union + never による網羅チェック はバグを防ぐ強力なパターンで、実務で積極的に使いましょう。次回はいよいよ最終回、Utility Types・ジェネリクス・型の再利用テクニックを総まとめします。📝 ▶ 次のステップ
次は「 」を解説します