TypeScriptのenumは非推奨?使い方と代替の判断フロー
TypeScriptのenumについて調べていると、「使い方」を説明する記事のすぐ隣に「非推奨」「使うな」という強い言葉が並んでいて、結局どうすればいいのか分からなくなってきますよね。
enumは公式に「非推奨」と宣言された機能ではありません。ただし数値enumを中心に弱点があり、多くの場面でunion型やas constオブジェクトのほうが扱いやすいのも事実です。
この記事では、数値・文字列enumの基本構文から、「非推奨」と言われる3つの理由、const enumの制約、enum・union・as constの選び方までを順に整理します。自分の環境でenumが本当に問題になるのかを切り分け、3つの選択肢から迷わず選べる状態を目指します。
この記事は次のような方におすすめです。
- 定数の集合をenumで書くべきか迷っている初〜中級者の方
- 「enum 非推奨」という記事を見て、自分のコードを直すべきか不安になった方
- 数値enumと文字列enumの違いや、const enumの落とし穴を正しく理解したい方
- enum・union型・as constのどれを使うべきか、判断基準がほしい方
読み終えるころには、「非推奨」という言葉に振り回されず、自分のプロジェクトの条件に合わせて、enumを残すか代替に移すかを判断できるようになります。
それでは、順を追って詳しく見ていきましょう!
- 未経験で後悔したくない
【実体験】未経験からITエンジニアに転職して後悔した話|4社経験してわかった「最初の選択ミス」 - 年収が低くて不安
エンジニア転職体験談|4社で年収250→510万にした全記録
TypeScriptのenumとは — 数値enumと文字列enumの基本
TypeScriptのenum(列挙型)は、関連する名前付き定数をひとまとめにする仕組みです。「方向」「ステータス」「曜日」のように、決まった選択肢の集合を意味のある名前で表したいときに使います。
enumには大きく分けて、メンバが数値になる数値enumと、文字列になる文字列enumの2種類があります。
// 数値enum:値を省略すると 0 から連番が振られる
enum Direction {
Up,
Down,
Left,
Right,
}
// 文字列enum:各メンバに文字列を明示する
enum Status {
Active = "ACTIVE",
Inactive = "INACTIVE",
}
enumは、型注釈やinterfaceのようにコンパイル時に消える「型だけの存在」とは違い、コンパイル後もJavaScriptのオブジェクトとして実行時に生成される点が特徴です。まずはそれぞれの書き方と性質を押さえていきます。
数値enumの書き方とデフォルトの連番
数値enumは、メンバ名だけを並べると先頭が0で、以降は1ずつ増える連番が自動で割り当てられます。
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
console.log(Direction.Up); // 0
console.log(Direction.Right); // 3
Direction.Up は 0 と等しくなります。開始値を明示すると、そこから先が連番になります。
enum Direction {
Up = 1, // ここを 1 にすると
Down, // 2
Left, // 3
Right, // 4
}
途中のメンバにだけ値を与えることもでき、その場合は指定したメンバの次から、その値を基準に連番が続きます。書き方は手軽ですが、数値そのものに意味はないため、ログやデータに 2 とだけ残っていても何を指すのか分かりにくいのが数値enumの素朴な弱点です。
文字列enumの書き方と使いどころ
文字列enumは、すべてのメンバに文字列の値を明示する必要があります。数値enumのような自動連番はありません。
enum Status {
Active = "ACTIVE",
Inactive = "INACTIVE",
Pending = "PENDING",
}
console.log(Status.Active); // "ACTIVE"
値が "ACTIVE" のような意味のある文字列なので、ログやAPIレスポンス、データベースに残った値をそのまま読んで意味が分かるのが最大の利点です。数値enumのように「この 2 は何だっけ」と定義を見に戻る手間がありません。
また、文字列enumには、数値enumが持つ逆方向マッピング(値からメンバ名を取得する仕組み)がありません。そのぶん生成されるオブジェクトもシンプルで、デバッグ時の見通しがよくなります。「決まった文字列の集合を安全に扱いたい」という用途では、数値enumより文字列enumのほうが素直に使えます。
なぜenumは「非推奨」と言われるのか — 3つの理由を条件付きで整理
最初にはっきりさせておくと、TypeScriptが公式にenumを一律「非推奨(deprecated)」と宣言したわけではありません。enumは現在も正式な言語機能です。それでも「使わないほうがよい」と語られるのは、主に数値enumの弱点と、enumがJavaScript標準にはない構文であることに理由があります。
よく挙げられる理由は次の3つです。
- 数値enumの逆方向マッピングと型安全性の穴
- enumがJavaScript標準の構文ではないこと
- Tree-shakingが効きにくくバンドルに残ること
大事なのは、これらの弱点が「いつ問題になり、いつ問題にならないか」は環境によって変わるという点です。ひとつずつ、自分の状況で実害になるかどうかを切り分けていきます。
数値enumは逆方向マッピングと型安全性に穴がある
数値enumは、メンバ名から値を取得できるだけでなく、値からメンバ名を取得する「逆方向マッピング」も自動で生成します。
enum Direction {
Up, // 0
Down, // 1
}
console.log(Direction.Up); // 0 (順方向:名前→値)
console.log(Direction[0]); // "Up" (逆方向:値→名前)
これはコンパイル後のJavaScriptを見ると仕組みが分かります。生成されるコードには、両方向の対応を埋め込む次のような行が含まれます。
// 生成されるJS(抜粋)
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Down"] = 1] = "Down";
Direction["Up"] = 0 で順方向を、その戻り値 0 をキーにした Direction[0] = "Up" で逆方向を同時に定義しています。この逆方向マッピングは数値enumにだけ存在し、文字列enumにはありません。便利に見えますが、意図しないキーアクセスを許してしまう余地にもなります。
型安全性の穴もあります。数値enumを引数に取る関数に、生の数値リテラルを渡せてしまう挙動が問題視されてきました。
enum Status {
Active, // 0
Inactive, // 1
}
function setStatus(s: Status) {
// ...
}
setStatus(5); // 例:範囲外の数値。バージョンによりエラー文言・可否が異なる
この「範囲外の数値リテラル代入」の扱いは、TypeScriptのバージョンによって異なります。比較的新しいバージョンでは範囲外の数値リテラル代入が拒否される方向に改良されており、Type '5' is not assignable to type 'Status'. のようなエラーになる場合があります(お使いのバージョンで実際の挙動を確認してください。文言や可否はバージョンによって異なります)。一方、文字列enumやunion型では、こうした「生の値を取り違える」余地がそもそも生まれにくくなります。ここが、数値enumを避ける一番分かりやすい動機です。
enumはJavaScript標準の構文ではない
TypeScriptは公式に「a typed superset of JavaScript(型付きのJavaScriptの上位互換)」と位置づけられています。型注釈やインターフェースは、コンパイル時に消えてプレーンなJavaScriptになります。一方で、enumは型情報を取り除いてもコードとして残り、実行時のオブジェクトを生成します。
enum Direction { Up, Down }
// 生成されるJS:enum はこのようなオブジェクトとして残る
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Down"] = 1] = "Down";
})(Direction || (Direction = {}));
interfaceやtype aliasが「型だけの存在」であるのに対し、enumだけは独自のランタイム構造を持ちます。これはJavaScript側に対応する標準構文がない、TypeScript独自の機能だからです。「型情報がほしいだけ」なのに実行時コードまで増えてしまうのは、型を消せば素のJavaScriptになるという設計思想と噛み合いません。
ただし、これが実害になるかどうかは用途次第です。実行時にも定数オブジェクトとして値を参照したい、列挙したい、という要件があるなら、実行時に残ること自体は目的に沿っています。「型だけ使えれば十分」な場面では、enumはやや過剰です。
Tree-shakingが効きにくくバンドルに残ることがある
enumは即時実行関数(IIFE)でオブジェクトを組み立てる形に展開されることがあります。この構造は、バンドラから見て「副作用のあるコード」と判定されやすく、未使用でも削除されずに残ることがあります。
Tree-shakingは、使っていないコードをビルド時に取り除く最適化です。しかしenumのIIFE展開は削除対象と判断されにくく、バンドルに残ることがあります。結果として、ほんの数個の定数のためにバンドルサイズがわずかに膨らむことになります。
この影響が問題になるかは、プロジェクトの性質で大きく変わります。
- フロントエンドでバンドルサイズを切り詰めたい場面:未使用enumが残るのは避けたい。実害になりやすい
- サーバーサイドや小規模なツール:バンドルサイズをそこまで気にしないため、実害は小さい
つまり「enumはTree-shakingに弱い」は事実ですが、影響が大きいのは主にバンドルサイズに敏感なフロントエンドだと条件付きで捉えるのが正確です。ランタイム残留を避ける手段として const enum がありますが、利用条件には注意が必要です。
const enumの落とし穴 — 使う前に知るべき制約
const enumは、enum宣言の前に const を付けるだけで使えます。狙いは明確で、ランタイムのオブジェクトを生成せず、使った箇所に値を直接インライン展開することです。
// 書いたコード(TypeScript)
const enum Direction {
Up,
Down,
}
const d = Direction.Up;
// 生成されるJS(標準的な tsc での出力):オブジェクトは消え、値が埋め込まれる
const d = 0 /* Direction.Up */;
上の const enum を標準的な tsc でコンパイルすると、Direction.Up が値 0 へ直接インライン展開され、Direction というオブジェクトはどこにも生成されません(/* Direction.Up */ は読みやすさのためのコメントで、実行されるコードではありません)。これがconst enumの狙いどおりの挙動で、ランタイムコードを増やさずに済みます。
ただし、この置き換えはどんな構成でも起こるとは限りません。const enumは「コンパイル時にインライン展開される」前提の機能なので、ファイルを1つずつ独立して変換するツール構成とは相性がよくありません。具体的には、次のような構成で注意が必要です。
isolatedModulesを有効にしている場合:ファイル単位の変換と相性が悪く、アンビエントなconst enumは参照できません。ツールによっては通常のenumとして出力される点にも注意が必要です。- Babel / esbuild など型情報を見ずに変換するトランスパイラ:Babelはデフォルトで通常のenum出力になり、
optimizeConstEnums: trueでインライン化します(export時はプレーンなオブジェクトになる点に注意)。esbuildも変換はできますが、.d.ts生成など型システム依存の処理は対象外です - 型消去だけで実行する構成:
erasableSyntaxOnlyや Node.js の型消去実行では、enum(const enumを含む)宣言そのものが対象外になり、enum自体が使えません
つまりconst enumはランタイムを消せる魅力的な機能ですが、使えない構成があり、挙動もツールやバージョンに依存します。Babel/esbuildを併用するモダンなビルド構成では避けるのが無難で、採用するなら自分の環境(TypeScriptバージョン、ビルドツール、各種オプション)で実際の出力を必ず確認してください。
enumを使わない場合の代替方針
enumをやめる場合、置き換え先は大きく2つに分かれます。「型だけの集合で十分か」「実行時に値を列挙したいか」で選ぶ手段が変わります。
- 型だけの集合で十分なら、文字列リテラルのunion型に置き換える。実行時コードを一切生まない
- 実行時にも値を列挙・反復したいなら、オブジェクトを
as constで固定し、そこから型を導く
どちらも、enumの弱点だった「不要なランタイムコード」「Tree-shakingで残りやすいこと」「数値の取り違え」を避けやすい書き方です。最小例で、それぞれがenumの何を解決するのかを対応づけて見ていきます。
型だけの集合で置き換える
「決まった文字列のどれか」を表したいだけなら、文字列リテラルのunion型が最もシンプルです。
type Status = "active" | "inactive";
function setStatus(s: Status) {
// ...
}
setStatus("active"); // OK
setStatus("x"); // 例:TS2345 Argument of type '"x"' is not assignable to parameter of type 'Status'.
union型は型注釈なので、コンパイルすると完全に消えます。enumのようなランタイムオブジェクトもIIFEも生成されず、Tree-shakingで残る心配はありません。値も "active" のような意味のある文字列そのものなので、ログやデータに残っても読めますし、数値の取り違えも起こりません。
一方で、union型は実行時の値ではなく型情報なので、Object.values() のような形でメンバを実行時に列挙することは、そのままではできません。「列挙は不要で、型として安全に絞れれば十分」というケースに向いています。実行時にこのunion値を "active" か "inactive" かで安全に判別したい場面では、型ガードという絞り込みのテクニックが役立ちます。
値の列挙が必要なときに置き換える
実行時にも値を一覧したい、Object.values() で回したい、という場合は、オブジェクトを as const で固定し、そこから型を導く書き方が定番です。
const Status = {
Active: "active",
Inactive: "inactive",
} as const;
type Status = (typeof Status)[keyof typeof Status];
// type Status = "active" | "inactive"
console.log(Object.values(Status)); // ["active", "inactive"]
ポイントは2つあります。as const を付けることで各値が "active" のようなリテラル型に固定され、(typeof Status)[keyof typeof Status] でそのオブジェクトの値の型(union型)を自動的に取り出せます。これにより、enumと同じように「列挙できる値の集合」を持ちながら、型としては安全なunion型を得られます。
Status は普通のオブジェクトなので、Object.values(Status) や Object.keys(Status) で実行時に列挙できます。enumの逆方向マッピングのような余計な構造は生まれず、生成されるJavaScriptも宣言したオブジェクトそのままです。「列挙したい」「でもenumの弱点は避けたい」という要求を両立できるのがこの書き方です。
enumを使うか代替に移るか — 判断フローと比較表
結論から言うと、「実行時に値を列挙したいか」「単なる文字列の集合か」「Tree-shakingを重視するか」の3つで、選ぶ手段はほぼ決まります。まず6つの軸で4つの選択肢を並べて比べます。
| 比較軸 | enum(数値) | enum(文字列) | union型 | as constオブジェクト |
|---|---|---|---|---|
| 型安全性 | 弱め(数値の混入余地) | 高い | 高い | 高い |
| 実行時コード生成 | あり(オブジェクト) | あり(オブジェクト) | なし(型のみ) | あり(普通のオブジェクト) |
| 列挙・反復のしやすさ | しやすい | しやすい | そのままでは不可 | しやすい(Object.values) |
| 逆引き(値 名前) |
あり(自動) | なし | なし | 自前で書けば可 |
| 記述量 | 少ない | やや少ない | 最も少ない | やや多い |
| Tree-shaking | 残りやすい | 残りやすい | 影響なし(消える) | 残りにくい |
この表をふまえて、次の順に質問していくと、選ぶべき手段にたどり着けます。
[スタート]
│
▼
Q1. 実行時に値を列挙・反復したい?(Object.values 等)
│
├─ Yes ─▶ as constオブジェクト(列挙できて型も安全)
│
└─ No
│
▼
Q2. 単なる文字列リテラルの集合で十分?
│
├─ Yes ─▶ union型(実行時コードを生まず最も軽い)
│
└─ No(数値や逆引きの都合がどうしても必要)
│
▼
Q3. Tree-shaking・バンドルサイズを重視する?
│
├─ Yes ─▶ as const か union を再検討(enumは残りやすい)
│
└─ No ──▶ enum(既存資産・チーム規約があるなら現実解)
このフローのとおり、多くのケースは union型 か as const に着地します。それでも、enumを使ってよい場面はあります。
- すでにenumを多用した大規模コードベースがある:一律の書き換えコストやリスクのほうが大きく、enum継続が現実解になりやすい
- チームの規約やライブラリのAPIがenum前提:足並みをそろえる価値がある
- 実行時の列挙が必須で、かつバンドルサイズを気にしない小規模な構成:enumの利便性が素直に活きる
逆に、フロントエンドでバンドルを軽くしたい、数値の取り違えを避けたい、isolatedModules 構成で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を中心に弱点があり、多くの場面でunion型やas constが有利
- 数値enumは逆方向マッピングと型安全性に穴があり、enumは型情報を取り除いてもランタイムのオブジェクトとして残り、Tree-shakingで落ちにくい
- ランタイムを消せるconst enumは、
isolatedModulesやBabel/esbuild構成で使えないなどの制約があり、将来の扱いも流動的 - 代替は、型だけで十分ならunion型、実行時に列挙したいならas constオブジェクトが基本
「非推奨」という言葉を鵜呑みにせず、自分のプロジェクトの条件に判断フローを当てて選ぶことが、enumと付き合ううえでいちばん確実な指針になります。
※本記事の本文案はAIを活用して作成していますが、記載している内容およびコードは筆者が実際に調査、検証・実行し、内容の正確性を確認した上で公開しています。






