TypeScriptを学んでいると、いつか必ず「enumは非推奨だ」という話を目にします。

enumって何?そもそもどう使うものなの?
「非推奨」って聞いたけど、使ったらダメなの?
代わりに何を使えばいいのか全然わからない…

この記事では、TypeScriptのenumの基本的な書き方から、非推奨とされる理由、そして代替手段の選び方まで、ひとまとめに整理します。「enumに遭遇したけど意味がわからない」という状態から「使うべき場面と避けるべき場面がわかる」状態を目指す内容です。

この記事はこんな人におすすめ!
  • TypeScriptのenumを初めて見て、何なのか知りたい人
  • 「enumは非推奨」という話を聞いて、理由が気になっている人
  • enumの代わりに何を使えばいいか迷っている人
  • 既存のコードにenumがあって、読めるようになりたい人

この記事を読み終えると、enumを使うべき場面と避けるべき場面が整理でき、代替となる書き方を自分で選択できるようになります。

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

TypeScriptのenumとは何か

TypeScriptのenumとは何か

enumは、関連する定数をひとまとまりにして名前を付ける仕組みです。「列挙型」とも呼ばれます。

たとえば、アプリの中でリクエストの状態を管理したいとします。"pending""success""error" という3つの文字列を使い回すとき、typo(タイポ)のリスクがあります。enumを使うと、これらをひとつのまとまりとして定義できます。

enumには大きく「数値enum」と「文字列enum」の2種類があります。

数値enumの書き方

数値enumは、最もシンプルな形です。値を省略すると、0から始まる整数が自動で割り当てられます。

enum Direction {
  Up,    // 0
  Down,  // 1
  Left,  // 2
  Right, // 3
}

const move = Direction.Up;
console.log(move); // 0

Direction.Up にアクセスすると、裏では 0 という数値が返ります。見た目はわかりやすいですが、この「裏で数値が動いている」という点が後述する問題の種になります。

文字列enumの書き方

文字列enumは、各メンバーに明示的に文字列を割り当てる形です。

enum Status {
  Pending = "PENDING",
  Success = "SUCCESS",
  Error = "ERROR",
}

const result = Status.Success;
console.log(result); // "SUCCESS"

値が文字列なので、デバッグ時にもそのまま意味が読み取れます。数値enumに比べて視認性が高い分、実際のコードでもよく見かける形です。

なぜ「非推奨」と言われるのか — 主な理由を整理する

なぜ「非推奨」と言われるのか — 主な理由を整理する

ここが本題です。「非推奨」という言葉は、TypeScriptの公式チームが明示的に「enumを使ってはいけない」と宣言しているわけではありません。コミュニティや実際のプロジェクトでの経験から「できれば避けた方がよい」とされることが多い、というのが正確なところです。

特に押さえておきたい4点を整理します。

① 数値enumは双方向マッピングを持つ

数値enumには、名前→値(Up → 0)と値→名前(0 → "Up")の両方向でアクセスできるという仕組みが自動で組み込まれています。これを「双方向マッピング(リバースマッピング)」と呼びます。

enum Direction {
  Up,    // 0
  Down,  // 1
}

console.log(Direction["Up"]); // 0  — 名前から値を引ける
console.log(Direction[0]);    // "Up" — 値から名前を逆引きできる

console.log(Object.keys(Direction));
// ["0", "1", "Up", "Down"] — 数値キーと名前キーが混在して返ってくる

Object.keys() を呼び出すと、数値と名前が混在した配列が返ってきます。「定義したメンバーの名前だけを取り出したい」という用途では直感的ではない挙動です。

文字列enumはこの双方向マッピングを持たないため、Object.keys() で数値キーが混入するという問題は起きません。ただ、文字列enumが全面的に安全かというとそうとも言い切れず、独自の特性があります。文字列enumは「公称型(Nominal Type)」として扱われるため、同じ文字列値を持っていても、enum以外の文字列リテラルはそのまま渡せません。

enum Status {
  Pending = "PENDING",
}

function check(s: Status) {}

check(Status.Pending); // OK
check("PENDING");      // エラー!同じ文字列でも受け付けない

ユニオン型の type Status = "PENDING" であれば "PENDING" という文字列リテラルをそのまま渡せますが、文字列enumではenumのメンバーを経由しないといけません。「文字列で書いてあるから安全」と思って使い始めると、この挙動に戸惑うケースがあります。

② 数値enumは型安全性に問題がある

双方向マッピングの影響もあり、数値enumは意図しない値を型チェックで弾けないケースがあるという問題も抱えています。

enum Direction {
  Up,    // 0
  Down,  // 1
}

function move(dir: Direction) {
  console.log(dir);
}

move(Direction.Up); // OK
move(100);          // バージョンによってはエラーにならない

TypeScriptのバージョンによって挙動は異なりますが、Direction 型の引数に 100 のような関係のない数値を渡しても、エラーにならない場合があります。「型で守る」というTypeScriptの目的に反しており、これが型安全性の問題として指摘されています。

③ JavaScriptの標準仕様から外れている

TypeScriptはJavaScriptのスーパーセット(上位互換)として設計されており、型アノテーションなどのTypeScript独自の書き方は、コンパイル後には消えてシンプルなJavaScriptになります。

ところが、enumはコンパイル後にもJavaScriptのコードとして残ります。しかもJavaScriptの標準仕様にはenumという機能は存在しないため、TypeScript独自のランタイムコードが生成されることになります。

実際にコンパイル後のコードを見ると、その量がわかります。

// TypeScript
enum Direction {
  Up,
  Down,
}
// コンパイル後のJavaScript
var Direction;
(function (Direction) {
  Direction[Direction["Up"] = 0] = "Up";
  Direction[Direction["Down"] = 1] = "Down";
})(Direction || (Direction = {}));

「TypeScriptはJavaScriptに型を足しただけ」というシンプルな原則から外れているのがenumです。

また、この余分に生成されるコードはTree-shakingの対象にもなりにくいという問題もあります。Tree-shakingとは、使われていないコードをビルド時に自動で取り除く最適化の仕組みのことです。enumのメンバーを一部しか使っていない場合でも、すべてのコードがバンドルに含まれてしまうことがあります。

④ const enumは現代の環境と相性が悪い

enumには const enum という記法もあります。

const enum Direction {
  Up,
  Down,
}

const enum はコンパイル時に値がインライン展開されるため、③で見たような余分なJavaScriptコードが生成されません。一見するとよさそうですが、現代のビルド環境ではそのままでは動かないことがあります

ViteやesbuildなどのビルドツールはTypeScriptのコンパイラ(tsc)とは別のトランスパイラを使うことが多く、isolatedModules が有効な標準的なビルド設定では const enum が正しく処理されずエラーになるケースがあります。「なぜエラーになるか」の仕組みの詳細よりも、「現代の開発環境では踏みやすい落とし穴がある」という事実として覚えておくのが実用的です。

enumの代わりに何を使うか

enumの代わりに何を使うか

では、enumの代わりに何を使えばよいのでしょうか。よく使われる代替手段が2つあります。

ユニオン型で代替する

最もシンプルな代替手段は、文字列リテラルのユニオン型です。

type Direction = "Up" | "Down" | "Left" | "Right";

function move(dir: Direction) {
  console.log(dir);
}

move("Up");    // OK
move("左");    // 型エラー
move("hello"); // 型エラー

コードが短く、TypeScriptの型システムだけで書けます。コンパイル後にJavaScriptとして残るコードもないため、ランタイムのコストがゼロです。

型チェックだけできれば十分な場面では、ユニオン型が一番手軽です。

なかむぅ
なかむぅ
ユニオン型についてはこちらの記事で詳しく解説しています。
TypeScriptのユニオン(Union)型とは?型ガードで「どの型か」を安全に絞り込む方法
TypeScriptのユニオン(Union)型とは?型ガードで「どの型か」を安全に絞り込む方法TypeScriptのユニオン型とは何かを基礎から解説。typeof・in・instanceofを使った型ガードの違いと使い分けも初心者向けにわかりやすく整理します。...

as constを使ったオブジェクトで代替する

もうひとつは、オブジェクトリテラルに as const を付ける方法です。

const Direction = {
  Up: "Up",
  Down: "Down",
  Left: "Left",
  Right: "Right",
} as const;

// 値の型をユニオン型として取り出す
type Direction = typeof Direction[keyof typeof Direction];
// 結果: "Up" | "Down" | "Left" | "Right"

function move(dir: Direction) {
  console.log(dir);
}

move(Direction.Up); // OK
move("hello");      // 型エラー

Direction.Up のようにドット記法でアクセスできるので、enumと近い書き心地になります。また、通常のJavaScriptオブジェクトとして定義されているため、Object.values() でループ処理したり、複数ファイルで import して使い回したりといった扱い方もできます。

ユニオン型とas const、どちらを選ぶか

どちらを使うか迷ったときの判断軸をまとめます。

条件 向いている方法
型チェックだけできれば十分(値を変数として参照しない) ユニオン型
Direction.Up のように名前でアクセスしたい as const オブジェクト
値を実行時に列挙・ループしたい as const オブジェクト
複数ファイルで値を共有・再利用したい as const オブジェクト

迷ったらまずユニオン型で書いてみて、「名前でアクセスしたい」「値をループで使いたい」と感じたらas constオブジェクトに切り替えるのがわかりやすいと思います。

enumを使っていい場面はあるか

enumを使っていい場面はあるか

「非推奨」と呼ばれることが多いとはいえ、enumが完全にダメというわけではありません

実際のコードベースでは、enumが使われているケースは今でも多くあります。既存のコードを読む機会は必ずあるので、「enumが書いてあっても読める」ようにしておくことは大切です。

また、チームの方針としてenumを採用しているプロジェクトもあります。その場合は、文字列enumを使い、数値enumは避けるという運用が一般的です。文字列enumは双方向マッピングの問題がなく、デバッグ時の視認性も高いからです。

  • 新しいコードを書くなら
    ユニオン型またはas constオブジェクトを選ぶのが現時点では無難
  • 既存コードを読む場合
    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プログラマーになれるはずです。


まとめ — enumとどう付き合うか

まとめ — enumとどう付き合うか

この記事で整理した内容をまとめます。

  • enumは関連する定数をひとまとめにして名前を付ける仕組みで、数値enumと文字列enumの2種類がある
  • 「非推奨」とされる主な理由は4点
    1. 数値enumは双方向マッピングを持つ
    2. 数値enumは型安全性に問題がある
    3. JavaScriptの標準仕様にないため余分なコードが生成される
    4. const enumは現代の環境と相性が悪い
  • 代替手段はユニオン型とas constオブジェクトの2つ。型チェックだけ必要ならユニオン型、名前でアクセスしたい・値を再利用したいならas constオブジェクト
  • enumは「絶対に使ってはいけない機能」ではなく、既存コードでも頻繁に登場する

enumとの付き合い方をひとことで言うと、「新規コードでは代替手段を選び、既存コードでは読める状態にしておく」が現実的な落とし所です。「非推奨」という言葉に惑わされすぎず、どういう問題があってなぜ代替手段が好まれるのかを理解した上で判断できれば十分です。

なかむぅ
なかむぅ
ユニオン型の応用(型ガードによる絞り込み)については、こちらの記事を参考にしてください。
TypeScriptのユニオン(Union)型とは?型ガードで「どの型か」を安全に絞り込む方法
TypeScriptのユニオン(Union)型とは?型ガードで「どの型か」を安全に絞り込む方法TypeScriptのユニオン型とは何かを基礎から解説。typeof・in・instanceofを使った型ガードの違いと使い分けも初心者向けにわかりやすく整理します。...

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