【TypeScript】enum(列挙型)完全ガイド|数値・文字列・const enum・union型との使い分けまで徹底解説

TypeScriptを使っていると、「ステータスを 012 で管理するのは辛い」「文字列定数をまとめたい」という場面が必ず出てきます。そこで役立つのが enum(列挙型) です。

enumを使うと関連する定数をひとまとめにできますが、「数値enumと文字列enumの違いは?」「const enum はいつ使う?」「union型や as const と何が違うの?」という疑問も多いです。本記事では、TypeScript の enum を基礎から実務パターンまで体系的に解説します。

この記事で学べること

  • 数値 enum・文字列 enum・ヘテロジニアス enum の基本と違い
  • const enum によるコンパイル最適化の仕組み
  • 数値 enum の逆引き(reverse mapping)の使い方
  • enum を型として使う・型の絞り込みとの組み合わせ
  • enum vs union 型 vs const assertion(as const)の使い分け
  • ステータス管理・API レスポンス分岐など実務でよく使うパターン
  • よくあるエラーと解決方法

前提知識:TypeScript の基本型(string, number, boolean など)を理解していることを前提とします。基礎から学びたい方は【TypeScript】型の書き方 完全入門をご参照ください。

スポンサーリンク

数値 enum の基本

enum キーワードで定義し、メンバーにはデフォルトで 0 から始まる整数が自動割り当てられます。

数値 enum の基本構文
// 基本的な数値 enum
enum Direction {
  Up    // 0
  Down  // 1
  Left  // 2
  Right // 3
}

// アクセス方法
const dir: Direction = Direction.Up;  // 0
console.log(Direction.Up);    // 0
console.log(Direction[0]);    // "Up"(逆引き)

開始値と手動割り当て

開始値を変更・手動で値を指定
// 1 始まりにする
enum Status {
  Pending  = 1,
  Active   = 2,
  Inactive = 3,
}

// 一部だけ指定(後続は自動インクリメント)
enum HttpStatus {
  OK        = 200,
  NotFound  = 404,
  ServerError = 500,
}

文字列 enum

各メンバーに文字列リテラルを割り当てます。数値 enum と異なり逆引きは存在しませんが、デバッグ時に値が意味をなすため実務でよく使われます。

文字列 enum の基本
enum Color {
  Red   = "RED",
  Green = "GREEN",
  Blue  = "BLUE",
}

console.log(Color.Red);    // "RED"
console.log(Color["RED"]); // undefined(文字列enumに逆引きなし)

// API レスポンスの方向性を定義するケース
enum SortOrder {
  Asc  = "asc",
  Desc = "desc",
}

function getUsers(order: SortOrder) {
  return fetch(`/api/users?sort=${order}`);
}
getUsers(SortOrder.Asc);

文字列 enum が数値 enum より好まれる理由:数値 enum はデバッグ時に 01 という値しか見えません。文字列 enum なら "asc""desc" のように意味が明確で、ログや API レスポンスで実際の値を確認しやすくなります。

const enum(コンパイル時最適化)

const enum はコンパイル時にメンバーの値がインライン展開され、生成される JavaScript にオブジェクトが残りません。パフォーマンスに敏感な場合に有効です。

通常 enum vs const enum の違い
// 通常 enum → コンパイル後にオブジェクトが残る
enum Direction { Up, Down, Left, Right }
const d = Direction.Up;  // → Direction.Up(参照が残る)

// const enum → コンパイル後にインライン展開
const enum Speed {
  Slow   = 1,
  Normal = 2,
  Fast   = 3,
}
const s = Speed.Fast;  // → const s = 3;(リテラルに展開)

const enum の制限:Babel や esbuild でトランスパイルする場合、const enum は正しく処理されないことがあります(Babel はデフォルトで const enum 非対応)。tsconfig の isolatedModules: true 環境では const enum はエラーになります。バンドラーを使うプロジェクトでは通常の enum か union 型を推奨します。

数値 enum の逆引き(Reverse Mapping)

数値 enum はコンパイル後に値からメンバー名を取得できる逆引きテーブルが自動生成されます。文字列 enum にはこの機能はありません。

逆引きの使い方
enum Status {
  Pending = 1,
  Active  = 2,
  Done    = 3,
}

// 数値 → 名前(逆引き)
console.log(Status[1]);  // "Pending"
console.log(Status[2]);  // "Active"

// 動的な値からラベル取得
function getStatusLabel(code: number): string {
  return Status[code] ?? "Unknown";
}
getStatusLabel(2);  // "Active"
getStatusLabel(9);  // "Unknown"

コンパイル後の JavaScript(逆引きテーブルの仕組み)

コンパイル後の JS(参考)
// TypeScript の enum は以下のような JS に変換される
var Status;
(function (Status) {
  Status[Status["Pending"] = 1] = "Pending";
  Status[Status["Active"]  = 2] = "Active";
  Status[Status["Done"]    = 3] = "Done";
})(Status || (Status = {}));
// Status[1] = "Pending" かつ Status["Pending"] = 1 の双方向マップが生成される

enum を型として使う

enum は型としても機能します。引数の型に enum を指定すると、定義されたメンバー以外の値を渡せなくなります。

enum を型として引数・戻り値に使う
enum UserRole {
  Admin  = "admin",
  Editor = "editor",
  Viewer = "viewer",
}

function checkPermission(role: UserRole): boolean {
  return role === UserRole.Admin;
}

checkPermission(UserRole.Admin);   // OK
// checkPermission("admin");  // TS Error: Argument of type 'string' is not assignable
// checkPermission("superuser");  // TS Error

switch 文との組み合わせ(網羅性チェック)

switch + never で網羅性を保証
function handleRole(role: UserRole): string {
  switch (role) {
    case UserRole.Admin:  return "管理者";
    case UserRole.Editor: return "編集者";
    case UserRole.Viewer: return "閲覧者";
    default:
      const _exhaustive: never = role;  // 未処理ケースがあればコンパイルエラー
      return _exhaustive;
  }
}

never による網羅性チェック:switch 文の default ブランチで never 型変数に代入することで、enum に新しいメンバーを追加した際に「対応する case がない」とコンパイルエラーで検知できます。実務でのバグ防止に非常に有効なパターンです。

enum vs union 型 vs const assertion の使い分け

TypeScript で定数群を管理する方法は enum だけではありません。union 型const assertion(as const)も広く使われます。それぞれの特徴を理解して使い分けましょう。

3種類の定数管理方法の比較
// ① enum
enum ColorEnum { Red = "red", Blue = "blue" }
type T1 = ColorEnum;  // ColorEnum 型

// ② union 型(文字列リテラル型)
type ColorUnion = "red" | "blue";
type T2 = ColorUnion;  // "red" | "blue"

// ③ const assertion(as const)
const COLOR = { Red: "red", Blue: "blue" } as const;
type T3 = typeof COLOR[keyof typeof COLOR];  // "red" | "blue"
観点 enum union 型 const assertion
JS 出力 ○(オブジェクト生成) ×(型のみ) ○(オブジェクト残る)
逆引き ○(数値のみ) × ×
補完
型安全性 ◎(より厳密)
ランタイムで値参照 ×
Babel/esbuild 互換 △(const enumは注意)
おすすめ場面 レガシー・逆引きが必要な場合 型だけで十分な場合 実務での第一選択肢

モダンな TypeScript では union 型や const assertion が好まれます。enum は生成される JS コードが増え、Babel との互換性問題もあるため、新規プロジェクトでは as const + union 型の組み合わせを推奨する意見が増えています。ただし、逆引きが必要な場合やレガシーコードとの互換性を保つ場合は enum も有効です。

const assertion を使った enum の代替パターン

as const で enum を代替する
// const assertion を使った enum 代替
const UserRole = {
  Admin:  "admin",
  Editor: "editor",
  Viewer: "viewer",
} as const;

// 値の union 型を取得
type UserRoleType = typeof UserRole[keyof typeof UserRole];
// type UserRoleType = "admin" | "editor" | "viewer"

// 使用例
function checkPermission(role: UserRoleType): boolean {
  return role === UserRole.Admin;
}

実務でよく使うパターン

API レスポンスのステータス管理

HTTP ステータスコードの enum
enum ApiStatus {
  Success     = 200,
  Created     = 201,
  BadRequest  = 400,
  Unauthorized = 401,
  NotFound    = 404,
  ServerError = 500,
}

function handleResponse(status: ApiStatus) {
  switch (status) {
    case ApiStatus.Success:
    case ApiStatus.Created:
      return "成功";
    case ApiStatus.Unauthorized:
      return "認証エラー";
    case ApiStatus.NotFound:
      return "見つかりません";
    default:
      return "エラーが発生しました";
  }
}

フォームの入力状態管理(文字列 enum)

フォーム状態管理
enum FormState {
  Idle      = "idle",
  Submitting = "submitting",
  Success   = "success",
  Error     = "error",
}

// React のステート管理との組み合わせ
const [state, setState] = useState<FormState>(FormState.Idle);

async function handleSubmit() {
  setState(FormState.Submitting);
  try {
    await submitForm();
    setState(FormState.Success);
  } catch {
    setState(FormState.Error);
  }
}

ビットフラグ(フラグ列挙)

権限管理など、複数のフラグを組み合わせて管理したい場合は2の累乗を使ったビットフラグが有効です。

ビットフラグ enum
enum Permission {
  None    = 0,
  Read    = 1 << 0,  // 1
  Write   = 1 << 1,  // 2
  Delete  = 1 << 2,  // 4
  Admin   = Read | Write | Delete,  // 7
}

// 権限チェック(ビット AND)
function hasPermission(userPerm: Permission, required: Permission): boolean {
  return (userPerm & required) === required;
}

const userPerm = Permission.Read | Permission.Write;  // 3
hasPermission(userPerm, Permission.Read);    // true
hasPermission(userPerm, Permission.Delete);  // false

よくあるエラーと解決方法

エラー / 問題 原因 解決方法
Element implicitly has an 'any' type(逆引き時) 数値インデックスの型が未定義 enum[value as number] または type assertion を使う
const enum が undefined になる Babel でトランスパイル時に const enum 未対応 通常の enum か union 型に変更。tsconfig に verbatimModuleSyntax 等を設定
文字列 enum に逆引きができない 文字列 enum は reverse mapping を持たない仕様 数値 enum にするか、別途 Record でマップを作成
enum の値が any になる(型引数で) enum を型変数の制約に使う場合の制限 T extends MyEnum ではなく union 型で制約を書く
よくあるエラーの修正例
// ❌ 文字列 enum で逆引き → undefined
enum Color { Red = "red" }
console.log(Color["red"]);  // undefined(逆引き不可)

// ✅ 解決: Record で逆引きマップを自前で作る
const ColorLabel: Record<Color, string> = {
  [Color.Red]: "赤",
};
ColorLabel[Color.Red];  // "赤"

// ❌ 数値 enum に存在しない値を渡す(型エラーにならないケース)
enum Dir { Up = 0, Down = 1 }
const d: Dir = 99;  // TypeScriptは数値なら代入を許す(注意)

// ✅ 解決: 文字列 enum にするか union 型にして型安全性を高める
enum StrDir { Up = "up", Down = "down" }
// const d: StrDir = "invalid";  // NG

まとめ

TypeScript enum 完全まとめ

  • 数値 enum:0 始まりの整数が自動割り当て。逆引き(reverse mapping)が使える
  • 文字列 enum:意味のある文字列を割り当て。デバッグしやすく実務で人気。逆引きなし
  • const enum:コンパイル時にインライン展開。Babel 環境では注意が必要
  • switch + never で enum の網羅性チェック。新メンバー追加漏れをコンパイル時に検出
  • 数値 enum は型が緩い(任意の数値を代入できる)。型安全性が重要なら文字列 enum か union 型を推奨
  • モダンな選択肢:union 型や const assertion(as const)は型のみで実現でき、JS の出力コードを減らせる
  • 逆引きが必要・ランタイムでキー列挙したい場合は enum が依然有効

enum の使い分けに迷ったら、まずユーティリティ型Record高度な型の const assertion との組み合わせを検討してみてください。また、switch 文での型ガードについては型の絞り込み(Type Narrowing)も参照ください。

よくある質問(FAQ)

enum と union 型はどちらを使えばいいですか?

モダンな TypeScript では union 型(文字列リテラル型)か const assertion を推奨します。enum は JS コードが増え、Babel との互換性問題もあります。ただし、ランタイムでキーや値を列挙したい場合・逆引きが必要な場合・レガシーコードとの互換性が必要な場合は enum が適しています。

数値 enum に任意の数値を代入できてしまうのはなぜですか?

TypeScript の数値 enum は数値型の拡張であり、型チェックが緩くなっています(例: Status = 99 がエラーにならない)。これは意図的な仕様ですが、型安全性を重視するなら文字列 enum か union 型のリテラル型を使うと厳密になります。

const enum はいつ使うべきですか?

tsc のみでコンパイルし、Babel や esbuild を使わないプロジェクトで、かつパフォーマンス最適化が必要な場合に限定して使用を検討してください。tsconfig に isolatedModules: true がある場合は const enum は使えません。迷ったら通常の enum を使いましょう。

enum のメンバーをオブジェクトのキーとして使えますか?

はい。Record<MyEnum, string> や computed property({ [MyEnum.Key]: value })の形で使えます。文字列 enum は特にオブジェクトキーとして使いやすく、型安全な辞書を作る場面でよく活用されます。