TypeScriptの基礎 - 列挙型と定数
概要
TypeScriptの列挙型 (enum) は、関連する定数値に名前を付けてグループ化するための機能である。
enumには数値enum、文字列enum、const enumの3種類があり、それぞれ異なるコンパイル結果と用途を持つ。
また、TypeScript独自の機能である as const アサーションを使用することで、enumに相当するパターンをより軽量に実現することもできる。
TypeScript 5.0以降では、全てのenumがユニオンenumとして統一的に扱われるようになり、型安全性が大幅に向上した。
具体的には、enum型の変数に対してenum外の値を代入した場合にエラーが発生するようになった。
また、TypeScript 5.4ではenumメンバーとしての NaN および Infinity の使用が禁止され、enum互換性チェックも厳格化された。
TypeScript 6.0ベータ以降もenumは引き続きサポートされているが、将来的なTypeScript 7.0 (Project Corsa、Go言語による書き直し) においても後方互換性が維持される予定である。
下表に、TypeScriptで利用可能なenum関連の種類と特徴を示す。
| 種類 | 逆マッピング | コンパイル後のコード | 主な用途 |
|---|---|---|---|
| 数値enum | あり | IIFEパターンのオブジェクト生成コード | 内部フラグ、ビットマスク、順序付き定数 |
| 文字列enum | なし | IIFEパターンのオブジェクト生成コード | APIレスポンス値、データベース値、可読性重視の定数 |
| const enum | なし | インライン展開 (ランタイムコードなし) | パフォーマンス最優先の定数 |
| 異種enum (Heterogeneous) | 数値メンバーのみあり | IIFEパターンのオブジェクト生成コード | 非推奨 特別な理由がない限り使用しない。 |
数値enum
数値enumは、メンバーに数値を割り当てるenumである。
デフォルトでは0から始まり、後続のメンバーへ自動的にインクリメントされる。
基本的な構文
数値enumの基本構文を以下に示す。
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
const move = Direction.Up; // 0
const name = Direction[0]; // "Up" (逆マッピング)
ビットマスクパターンを使用する例を以下に示す。
ビットシフト演算子を使用することで、フラグの組み合わせを表現できる。
enum Permission {
None = 0,
Read = 1 << 0, // 1
Write = 1 << 1, // 2
Execute = 1 << 2, // 4
Admin = Read | Write | Execute // 7
}
const userPerm = Permission.Read | Permission.Write; // 3
const canRead = (userPerm & Permission.Read) !== 0; // true
自動インクリメントと明示的な値の指定
enumメンバーに明示的に値を指定することができる。
明示的に値を指定したメンバー以降は、そのメンバーの値から+1ずつ自動インクリメントされる。
enum Direction {
Up = 1, // 1 (明示的)
Down, // 2 (自動インクリメント)
Left, // 3 (自動インクリメント)
Right // 4 (自動インクリメント)
}
enum HttpStatus {
OK = 200,
Created = 201,
BadRequest = 400,
Unauthorized = 401,
NotFound = 404,
InternalServerError = 500
}
数値enumの逆マッピング
数値enumは逆マッピングをサポートしており、値からメンバー名を取得できる。
これは、数値enumのコンパイル結果が双方向のマッピングを持つオブジェクトを生成するためである。
enum Direction {
Up = 1,
Down,
Left,
Right
}
// 通常のマッピング (名前 -> 値)
console.log(Direction.Up); // 1
// 逆マッピング (値 -> 名前)
console.log(Direction[1]); // "Up"
console.log(Direction[2]); // "Down"
コンパイル後のJavaScriptコードは、IIFEパターンで生成される。
// コンパイル結果
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 1] = "Up";
Direction[Direction["Down"] = 2] = "Down";
Direction[Direction["Left"] = 3] = "Left";
Direction[Direction["Right"] = 4] = "Right";
})(Direction || (Direction = {}));
TypeScript 5.0以降では、型安全性が向上し、enum型の変数にenum外の値を代入するとエラーになる。
enum Status {
Pending = 0,
Active = 1
}
function process(status: Status) {
if (status === 99) { // TypeScript 5.0以降: エラー
console.log("Unknown");
}
}
文字列enum
文字列enumは、各メンバーに明示的に文字列値を指定するenumである。
APIレスポンスやデータベース値との対応が容易で、デバッグ時の可読性が高い。
基本的な構文
文字列enumでは、全てのメンバーに対して明示的に文字列値を指定する必要がある。
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT"
}
const color = Color.Red; // "RED"
console.log(color); // "RED"
文字列enumの特徴
文字列enumと数値enumの主な違いを以下に示す。
- 逆マッピング非対応
- 文字列enumは逆マッピングを持たない。値からメンバー名を取得することはできない。
- 自動インクリメント非対応
- 全てのメンバーに対して明示的に値を指定する必要がある。
- デバッグ時の可読性
- 文字列値はそのまま表示されるため、数値と比べてデバッグ時に意味が分かりやすい。
文字列enumをAPIレスポンスの状態管理に使用する例を以下に示す。
enum RequestState {
Idle = "IDLE",
Loading = "LOADING",
Success = "SUCCESS",
Error = "ERROR"
}
function handleState(state: RequestState): void {
switch (state) {
case RequestState.Loading:
console.log("読み込み中...");
break;
case RequestState.Success:
console.log("成功");
break;
case RequestState.Error:
console.log("エラーが発生しました");
break;
}
}
定数enum (const enum)
const enum は、コンパイル時にインライン展開されるenumである。
ランタイムにはenumオブジェクトが生成されないため、バンドルサイズを削減できる。
const enumの基本
const キーワードをenumの前に付けることで、const enumを定義できる。
const enum Suit {
Clubs = 0,
Diamonds = 1,
Hearts = 2,
Spades = 3
}
const hand1 = Suit.Diamonds; // コンパイル結果 : var hand1 = 1;
const hand2 = Suit.Hearts; // コンパイル結果 : var hand2 = 2;
コンパイル結果の違い
通常のenumとconst enumのコンパイル結果の違いを以下に示す。
- 通常のenumのコンパイル結果
// ソースコード enum Suit { Clubs = 0, Diamonds = 1 } const value = Suit.Diamonds; // コンパイル結果 (IIFEパターンのオブジェクトが生成される) var Suit; (function (Suit) { Suit[Suit["Clubs"] = 0] = "Clubs"; Suit[Suit["Diamonds"] = 1] = "Diamonds"; })(Suit || (Suit = {})); var value = Suit.Diamonds; // 1
- const enumのコンパイル結果
// ソースコード const enum Suit { Clubs = 0, Diamonds = 1 } const value = Suit.Diamonds; // コンパイル結果 (enumオブジェクトは生成されず、値がインライン展開される) var value = 1;
const enumの制約と注意点
const enumには以下の制約がある。
| 制約 | 説明 |
|---|---|
| 逆マッピング非対応 | const enumはインライン展開されるため、逆マッピングを使用できない。 |
| computed memberの使用不可 | const enumのメンバーには、リテラル値のみを使用できる。 計算式(computed member)は使用できない。 |
preserveConstEnums オプション |
tsconfig.jsonファイル で preserveConstEnums: true を設定することで、const enumのランタイムコードを生成することができる。 |
| 型宣言ファイル(.d.ts)の制限 | 外部ライブラリの型宣言ファイルに定義されたconst enumを使用する場合、isolatedModules オプションが有効な環境では問題が発生することがある。
|
// computed memberは使用不可
const enum Invalid {
A = "hello".length // エラー: const enumはcomputed memberを使用できない
}
// リテラル値のみ使用可能
const enum Valid {
A = 1,
B = 2,
C = A + B // 他のconst enumメンバーの参照は可能
}
constアサーション (as const)
as const は、オブジェクトや配列のリテラル型を保持するためのTypeScriptのアサーション構文である。
enumに相当するパターンを、通常のTypeScriptオブジェクトで実現するために広く使用される。
as constによる定数オブジェクトの定義
as const を使用することにより、オブジェクトの全プロパティが readonly かつリテラル型として扱われる。
const Status = {
Pending: "pending",
Active: "active",
Completed: "completed"
} as const;
// as constなし : { Pending: string; Active: string; Completed: string; }
// as constあり : { readonly Pending: "pending"; readonly Active: "active"; readonly Completed: "completed"; }
const current = Status.Active; // 型: "active" (string ではなくリテラル型)
as constと型の抽出
typeof と keyof を組み合わせることにより、定数オブジェクトからキーの型と値の型を抽出できる。
const Status = {
Pending: "pending",
Active: "active",
Completed: "completed"
} as const;
// キーの型を抽出
type StatusKeys = keyof typeof Status;
// "Pending" | "Active" | "Completed"
// 値の型を抽出
type StatusValues = (typeof Status)[keyof typeof Status];
// "pending" | "active" | "completed"
satisfies と組み合わせることにより、型チェックを維持しながらリテラル型を保持できる。
type ColorDef = { hex: string; name: string; };
const Colors = {
red: { hex: "#FF0000", name: "Red" },
green: { hex: "#00FF00", name: "Green" },
blue: { hex: "#0000FF", name: "Blue" }
} as const satisfies Record<string, ColorDef>;
// satisfiesにより、Record<string, ColorDef>に適合しているかチェックされる
// as constにより、リテラル型が保持される
type RedHex = typeof Colors.red.hex; // "#FF0000" (リテラル型)
enumを避けるべき場面
TypeScriptのenumは便利な機能だが、バンドルサイズやtree-shakingの点から、代替パターンが推奨される場面もある。
enumとユニオン型の比較
文字列enumとユニオン型を使用した場合の比較を以下に示す。
// as const版 (推奨)
const RequestState = {
Idle: "IDLE",
Loading: "LOADING",
Success: "SUCCESS",
Error: "ERROR"
} as const;
type RequestState = typeof RequestState[keyof typeof RequestState];
// enum版
enum RequestState {
Idle = "IDLE",
Loading = "LOADING",
Success = "SUCCESS",
Error = "ERROR"
}
// ユニオン型版
type RequestState = "IDLE" | "LOADING" | "SUCCESS" | "ERROR";
下表に、enumとユニオン型の特徴を比較する。
| 観点 | enum | ユニオン型 | as constパターン |
|---|---|---|---|
| 記述の簡潔さ | 中程度 | 高い | 中程度 |
| 逆マッピング | 数値enumのみあり | なし | なし |
| tree-shaking | 非対応 (IIFEのため) | 対応 | 対応 |
| バンドルサイズ | 大きい | なし | なし |
| 型安全性 | 高い (5.0以降) | 高い | 高い |
| ランタイム参照 | 可能 | 不可 | 可能 |
| 追加の型定義 | 不要 | 不要 | 必要 (type定義) |
Tree-shakingとバンドルサイズへの影響
enumのコンパイル結果であるIIFEパターンは、バンドラ (webpack、Rollup等) のtree-shakingの対象にならない。
enumを使用するだけでランタイムコードが生成されるため、使用しない定数もバンドルに含まれてしまう。
// enumのコンパイル結果 (IIFEはtree-shakingされない)
var Color;
(function (Color) {
Color["Red"] = "RED";
Color["Green"] = "GREEN";
Color["Blue"] = "BLUE";
})(Color || (Color = {}));
// as constのコンパイル結果 (通常のオブジェクトのため、tree-shakingが効く)
const Color = {
Red: "RED",
Green: "GREEN",
Blue: "BLUE"
};
推奨パターン
用途に応じた推奨パターンを以下に示す。
| 用途 | 推奨パターン |
|---|---|
| ランタイムでのenum参照が必要な場合 | 文字列enum または as const パターンを使用する。
|
| パフォーマンスとバンドルサイズを最優先する場合 | const enum を使用する。ただし、型宣言ファイルとの互換性に注意する。 |
| フロントエンド(Webアプリ)での使用 | as const パターンを推奨する。tree-shakingが効き、バンドルサイズを最小化できる。 |
| ビットマスクや順序付き定数 | 数値enumを使用する。 逆マッピングが利用でき、ビット演算も自然に記述できる。 |
下表に、enum・const enum・as constの使い分けの指針を示す。
| 用途 | 推奨パターン | 理由 |
|---|---|---|
| APIレスポンス値の管理 | as constパターン | tree-shakingが効き、バンドルサイズを削減できる。 |
| 内部フラグ・ビットマスク | 数値enum | 逆マッピングとビット演算が利用できる。 |
| パフォーマンス重視の定数 | const enum | インライン展開によりランタイムコストがゼロ |
| 外部公開ライブラリのAPI | 文字列enum | 型定義が明確で、利用者にとって分かりやすい。 |
| コンパイル時のみ使用する定数 | const enum | ランタイムコードが生成されない。 |
| 型と値を同名で管理したい場合 | as constパターン (型と値の同名定義) | TypeScriptの型と値の名前空間を分離して活用できる。 |
関連情報