【入門編】列挙型(Enum)の代替案:なぜTypeScriptではリテラル型のユニオンが好まれるのか – TypeScript コア・型システムの基礎解析バイブル

こんにちは!フロントエンドからNode.jsの深層まで、TypeScriptと日々向き合っている先輩エンジニアです。

今回は、TypeScriptを学び始めた方から非常によく質問を受ける「列挙型(Enum)と、その代替案であるリテラル型のユニオン」についてお話しします。

他のプログラミング言語(JavaやC#、Swiftなど)を触ったことがある方なら、「おっ、Enumがあるじゃん!」と親しみを感じて使いたくなりますよね。でも、実は近年のモダンなTypeScript開発では、「Enumは使わずに、リテラル型のユニオン(Union)を使おう」というプラクティスが強く推奨されています。

「えっ、なんでわざわざ便利なEnumを使わないの?」って思いますよね。
ここをクリアすれば、TypeScriptの型システムの「本質」がグッと見えてきて、コードの安全性も一気に上がりますよ。一緒に優しく紐解いていきましょう!

—

1. そもそもEnum(列挙型)ってどう使うんだっけ?

まずは、お馴染みのEnumのおさらいからですね。
例えば、アプリのユーザー権限を管理する場面を想像してください。

// 伝統的なEnumの定義
enum Role {
Admin = “ADMIN”,
User = “USER”,
Guest = “GUEST”,
}

function handleAccess(role: Role) {
if (role === Role.Admin) {
console.log(“管理者権限です。全システムを操作できます。”);
} else {
console.log(“一般ユーザーまたはゲストです。”);
}
}

// 使うときはこう書きます
handleAccess(Role.Admin);

一見、とてもすっきりしていて読みやすいコードですよね。
ですが、ここからがTypeScriptの「コンパイルの裏側」を知るための重要なポイントです。

—

2. 【核心】なぜTypeScriptではEnumが嫌われるのか?

他の言語のEnumは、だいたいが「コンパイルされると消える(または単なる数値や定数になる)」ものが多いです。しかし、TypeScriptのEnumには、言語仕様上の「ちょっと厄介な特殊性」があります。

理由①:Enumは「ただの型」ではなく「JavaScriptのコード」として実体を出力してしまう

TypeScriptは基本的に「コードをJavaScriptにコンパイルするときに型を消し去る」言語です。しかし、Enumだけは例外的にJavaScriptのオブジェクト(実体)を生成します。

先ほどの `Role` EnumをTypeScriptでコンパイルすると、JavaScriptでは次のような即時関数(IIFE)に変換されます。

// TypeScriptのEnumがコンパイルされた後のJavaScript(イメージ)
var Role;
(function (Role) {
Role[“Admin”] = “ADMIN”;
Role[“User”] = “USER”;
Role[“Guest”] = “GUEST”;
})(Role || (Role = {}));

「おや、JavaScriptとして動くなら別にいいじゃない?」と思いますよね。
しかし、これによって「バンドルサイズ(ファイルサイズ)が無駄に増える」「他のファイルとのインポート/エクスポートでトランスパイル時の挙動が複雑になる」といったデメリットが生じます。型定義だけなのに、ランタイム(実行時)のメモリを消費するコードが生成されてしまうのは、アーキテクトとしては少しモヤっとするポイントなんです。

理由②:数値Enumの危険な「型安全性の穴」

特に文字列ではなく、数値を割り当てる「数値Enum(Numeric Enum)」を使うときには注意が必要です。

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

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

// ⚠️ TypeScriptの型チェックをすり抜けてしまうバグの温床
move(99); // 99なんて方向は定義していないのに、エラーにならない!

えっ、嘘でしょ!?と思いましたよね。
数値Enumの場合、TypeScriptは「定義された数値以外の数値(上の例では `99` など)」を渡しても、JavaScriptの数値型であればコンパイルエラーにしないという仕様(構造的型付けの側面)を持っています。これでは「型安全」とは言えませんよね。

—

3. 救世主:「リテラル型のユニオン」による代替案

そこでモダンなTypeScript開発で主流になっているのが、「文字列リテラル型(または数値リテラル型)のユニオン(Union)」です。

まずは、先ほどの `Role` Enumをリテラル型のユニオンで書き換えてみましょう。

// Enumを使わず、ただの「文字列の直書き(リテラル)」を型にする
type Role = “ADMIN” | “USER” | “GUEST”;

function handleAccess(role: Role) {
if (role === “ADMIN”) {
console.log(“管理者権限です。全システムを操作できます。”);
} else {
console.log(“一般ユーザーまたはゲストです。”);
}
}

// 使うときは直接文字列を渡す
handleAccess(“ADMIN”);

これの何が素晴らしいのか、詳しく見ていきましょう!

メリット①:JavaScriptのコードが出力されない(完全な「型」の世界)

`type Role = “ADMIN” | “USER” | “GUEST”;` は、純粋にTypeScriptの型定義です。
JavaScriptにコンパイルされると、この行は綺麗に跡形もなく消え去ります。ランタイムの無駄なコードを1バイトたりとも生み出さない、非常にエコでクリーンなアプローチです。

メリット②:圧倒的な型安全性とエディタの補完

リテラル型のユニオンを使えば、定義されていない値を渡そうとした瞬間に、TypeScriptのコンパイラが容赦なくエラーを出してくれます。

// ❌ 存在しない文字列を渡そうとすると、即座にコンパイルエラー!
handleAccess(“SUPER_ADMIN”);
// 💡 エラーメッセージ:
// 型 ‘”SUPER_ADMIN”‘ の引数を型 ‘Role’ のパラメータに割り当てることはできません。

もちろん、VSCodeなどのエディタを使っていれば、引数を書くときに `”`(ダブルクォーテーション)を入力した瞬間、`”ADMIN” | “USER” | “GUEST”` の候補がズラッとポップアップで自動補完されます。開発体験も最高に快適です。

—

4. 「でも、定数としてまとめたいときはどうするの?」

Enumを使わない場合のよくある疑問が、「値のリストを一箇所にまとめて管理したいときはどうすればいいの?」という点です。

Enumの代わりに、`as const`(タプルやオブジェクトのイミュータブル化)を組み合わせた「オブジェクト+型推論」のテクニックを使いましょう。これが実務で一番よく使われる黄金パターンです。

// 1. 定数オブジェクトを定義し、’as const’ で読み取り専用(リテラル型)にする
const Role = {
Admin: “ADMIN”,
User: “USER”,
Guest: “GUEST”,
} as const;

// 2. typeof と keyof を使って、オブジェクトから自動的に型を抽出する
type Role = typeof Role[keyof typeof Role];
// ↑ これで型は “ADMIN” | “USER” | “GUEST” になります!

// 使い方
function handleAccess(role: Role) {
console.log(`現在のロール: ${role}`);
}

// 定数オブジェクト経由で安全に呼び出せる
handleAccess(Role.Admin); // OK (“ADMIN” が渡る)

//もちろんタイポや存在しない値はエラーになる
// handleAccess(“OWNER”); // ❌ コンパイルエラー!

この書き方であれば、
1. `Role.Admin` のようにドットつなぎでコード補完を効かせながら値を使える
2. 裏側では厳密なリテラル型ユニオンとして機能する
3. 余計なランタイムコードを増やさない

という、Enumの「書きやすさ」とリテラル型の「安全性・軽さ」のいいとこ取りができます!

—

まとめ:これからのTypeScriptの指針

今回は、Enumの裏側の挙動から、リテラル型ユニオンが愛される理由までを深く掘り下げて解説しました。

  • Enum: ランタイムにオブジェクトを出力するため、バンドルサイズや数値Enumの挙動に注意が必要。
  • リテラル型のユニオン: コンパイル時に消えるため安全かつ軽量。モダンなTS開発のスタンダード。
  • `as const` パターン: Enumのような書き心地と、リテラル型の安全性を両立させる最高の実装手法。

「他の言語の癖で、ついつい `enum` って書いちゃいそう…」という方も、今日からぜひ `typehoge = …` や `as const` を使ったリテラル型ユニオンに挑戦してみてください。
型安全の海が、ぐっとクリアに見渡せるようになりますよ。

ここをマスターすれば、あなたのTypeScriptはもう中級者の領域です。一緒に最高のコードベースを作っていきましょう!

タイトルとURLをコピーしました