1. ホーム
  2. TypeScript

【TypeScript】enum(列挙型)の使い方|定数をまとめて管理する方法

Share

プログラムの中で「状態」や「種類」を表すために、0"active" のような値をあちこちに直接書いていると、打ち間違いや意味の分かりにくさに悩まされます。TypeScript の enum(列挙型)は、こうしたバラバラの定数をひとまとめにして、意味のある名前を付けて管理するための機能です。この記事では、数値 enum と文字列 enum の書き方から、値の参照方法、const enum やユニオン型との使い分けまで、初心者がつまずきやすいポイントを押さえながら解説します。

enum とは何か・なぜ使うのか

enum(enumeration=列挙)は、関連する複数の定数をひとつのまとまりとして定義するための型です。たとえば「注文の状態」には「保留中」「発送済み」「配達完了」といった限られた種類があります。これらを 012 のような裸の数字で扱うと、コードを読んでも 1 が何を意味するのか分かりません。

enum を使うと、それぞれの値に Pending(保留中)のような意味のある名前を付けられます。名前でアクセスできるので読みやすく、決められた値以外を誤って渡すのを型チェックで防げます。まずは基本の形を見てみましょう。

order-status.ts
// 注文の状態を enum で定義する
enum OrderStatus {
  Pending,   // 保留中
  Shipped,   // 発送済み
  Delivered, // 配達完了
}

// enum の値を使う
const status: OrderStatus = OrderStatus.Shipped;

if (status === OrderStatus.Shipped) {
  console.log('発送済みです');
}

OrderStatus.Shipped のように名前でアクセスできるため、コードの意図が一目で伝わります。status の型を OrderStatus にしておくと、この enum に定義されていない値を代入しようとするとコンパイルエラーになり、タイプミスを未然に防げます。

数値 enum(自動採番と初期値の指定)

enum のメンバーに値を指定しない場合、TypeScript は先頭を 0 として順番に自動採番します。先ほどの OrderStatus では Pending0Shipped1Delivered2 になります。

初期値を指定することもできます。あるメンバーに数値を割り当てると、それ以降のメンバーはその値から続けて採番されます。

numeric-enum.ts
// 何も指定しなければ 0 から自動採番
enum Direction {
  Up,    // 0
  Down,  // 1
  Left,  // 2
  Right, // 3
}

// 初期値を指定すると、そこから続けて採番される
enum StatusCode {
  Success = 200,
  NotFound = 404, // ここは明示的に指定
  ServerError,    // 405(直前の 404 + 1)
}

console.log(Direction.Right);  // 3
console.log(StatusCode.NotFound); // 404

StatusCode のように途中で初期値を指定すると、ServerError は自動的に 405 になります。意図しない番号にならないよう、初期値を混ぜて使うときは採番の連続に注意してください。

文字列 enum とその方が安全な理由

数値 enum は手軽ですが、値が単なる連番になるため、ログやデータベースに保存したときに 12 だけを見ても意味が分かりません。そこで、各メンバーに文字列を明示的に割り当てるのが文字列 enum です。文字列 enum では自動採番がないため、すべてのメンバーに値を指定する必要があります。

string-enum.ts
// 文字列 enum は各メンバーに値を明示する
enum Color {
  Red = 'RED',
  Green = 'GREEN',
  Blue = 'BLUE',
}

const favorite: Color = Color.Blue;

console.log(favorite); // "BLUE"(値そのものが意味を持つ)

文字列 enum が数値 enum より安全とされるのは、値そのものが意味を持ち、デバッグやデータ保存のときに読みやすいからです。console.log(favorite)"BLUE" と出力されれば一目で分かりますが、数値 enum だと 2 としか出ず、定義を見に行かないと意味が分かりません。また、数値 enum には後述するリバースマッピングによって余分なキーが生成されますが、文字列 enum にはそれがなく、値の集合がシンプルに保たれます。次の表に両者の違いをまとめます。

観点数値 enum文字列 enum
値の指定省略可(0 から自動採番)各メンバーに明示が必要
値の可読性低い(01 …)高い("RED" など)
リバースマッピングあり(値→名前も引ける)なし
向いている場面順序や大小に意味がある値状態やカテゴリの識別子

値の参照とリバースマッピング

enum のメンバーには OrderStatus.Shipped のようにドット記法でアクセスします。加えて数値 enum には、値から名前を逆引きできる「リバースマッピング」という仕組みがあります。これは数値 enum のときだけ有効で、文字列 enum では生成されません。

reverse-mapping.ts
enum Direction {
  Up,    // 0
  Down,  // 1
}

// 名前 → 値
console.log(Direction.Up);        // 0

// 値 → 名前(リバースマッピング。数値 enum のみ)
console.log(Direction[0]);        // "Up"
console.log(Direction[Direction.Up]); // "Up"

数値 enum はコンパイル後、{ Up: 0, Down: 1, 0: "Up", 1: "Down" } のように名前と値の両方向のキーを持つオブジェクトになります。このため Direction[0] で名前 "Up" を取り出せます。便利な反面、余分なキーが増える点は覚えておきましょう。文字列 enum ではこの逆引きが使えないため、値から名前を取りたい場合は自分で対応を用意する必要があります。

const enum との違い

enum の前に const を付けた const enum という書き方もあります。通常の enum はコンパイル後に実体のオブジェクトを生成しますが、const enumコンパイル時に使用箇所へ値がそのまま埋め込まれ(インライン展開)、オブジェクト自体は出力されません。その分、生成される JavaScript が小さくなります。

const-enum.ts
const enum Size {
  Small = 1,
  Medium = 2,
  Large = 3,
}

const box = Size.Medium;
// コンパイル後は const box = 2; のように値が直接埋め込まれる

const enum は軽量ですが、実体のオブジェクトが残らないため、リバースマッピングが使えなかったり、一部のビルド構成(トランスパイルのみで型チェックしない環境など)で正しく展開されず問題になることがあります。特別な理由がなければ、まずは通常の enum を使うのが無難です。

ユニオン型リテラルとの使い分け

「決まった値のどれか」を表す方法は enum だけではありません。文字列リテラルを | でつないだユニオン型でも同じことができます。こちらは新しいオブジェクトを生成せず、純粋に型の情報だけで完結するのが特徴です。

union-type.ts
// ユニオン型リテラルで「取りうる値」を表現する
type Status = 'active' | 'inactive' | 'pending';

function setStatus(status: Status) {
  console.log(`現在の状態: ${status}`);
}

setStatus('active');   // OK
// setStatus('deleted'); // エラー: 定義にない値は渡せない

ユニオン型はコンパイル後に何も出力されず、'active' のような値をそのまま書けるため手軽です。値を集約した名前空間(Color.Red のようなアクセス)が欲しい、あるいはリバースマッピングを使いたい場合は enum が向いています。逆に、余分な JavaScript を出力したくない、値を素朴な文字列として扱いたい場合はユニオン型が適しています。近年の TypeScript では、シンプルな識別子の集合にはユニオン型リテラルを選ぶケースも増えています。用途に応じて選び分けましょう。

enum を使うときに注意したいこと

数値 enum の値を安易に外部と連携させない

数値 enum は自動採番のため、途中にメンバーを挿入すると以降の値がすべてずれてしまいます。この数値をデータベースや API のレスポンスにそのまま保存していると、後からメンバーを追加したときに古いデータとの対応が崩れる恐れがあります。外部に保存する識別子には、値が固定される文字列 enum を使うと安全です。

enum のメンバーを型として使うときの書き方

OrderStatus のような enum 名は、型としても値としても使えます。関数の引数を「この enum のいずれか」に限定したいときは、型注釈に enum 名をそのまま書きます。特定のメンバーだけを許可したい場合は、OrderStatus.Shipped のようにメンバーを型として指定することもできます。

enum-as-type.ts
enum OrderStatus {
  Pending,
  Shipped,
  Delivered,
}

// 引数を OrderStatus のいずれかに限定する
function notify(status: OrderStatus) {
  console.log(`状態が ${OrderStatus[status]} に変わりました`);
}

notify(OrderStatus.Delivered);

このように enum 名を型注釈に使えば、定義された値以外を渡すコードはコンパイル時にはじかれます。数値 enum なら OrderStatus[status] でメンバー名の文字列も取得できるので、ログ出力にも役立ちます。

まとめ

enum は、関連する定数をひとつにまとめ、意味のある名前で扱うための TypeScript の機能です。値を省略すると 0 から自動採番される数値 enum と、各メンバーに文字列を割り当てる文字列 enum があり、データ保存やデバッグのしやすさから文字列 enum の方が安全な場面が多くあります。数値 enum は値から名前を逆引きできるリバースマッピングを備え、const enum はコンパイル後に値がインライン展開される軽量版です。単純な識別子の集合であれば type Status = 'active' | 'inactive' のようなユニオン型リテラルも有力な選択肢です。それぞれの特徴を理解し、用途に合った方法で定数を管理していきましょう。

参考ページ