課題7: Enum (列挙型) と Union Types (共用体型) (所要時間目安: 20‐30分) - hideki5123/myts GitHub Wiki
今回は、特定の関連する値のセットを定義するための Enum (列挙型) と、それに似た目的で使われることもある Union Types (特に文字列リテラルユニオン) について学びます。それぞれの特徴、C#との違い、そしてTypeScriptにおける使い分けのポイントを理解しましょう。
C#との比較:
- Enum (列挙型):
- TypeScript: C#の
enum
と非常に似ています。- 数値Enum:
enum Direction { Up, Down, Left, Right }
(デフォルトでUp=0, Down=1, ...
)。- 逆引きマッピング (Reverse Mapping): TypeScriptの数値Enumは、値からキー名への逆引きマッピング(例:
Direction[0]
が"Up"
)を自動的に生成します。これはC#のEnumにはない特徴です。ただし、この機能は主にデバッグやロギング、シリアライズされた値から名前を得るためにあり、通常のEnum値の比較(例:if (dir === Direction.Up)
)には、Enumメンバー名 (Direction.Up
) を直接使うのが最も推奨されます。
- 逆引きマッピング (Reverse Mapping): TypeScriptの数値Enumは、値からキー名への逆引きマッピング(例:
- 文字列Enum:
enum LogLevel { ERROR = "エラーメッセージ", INFO = "情報" }
のように、メンバーに任意の文字列値を割り当てることができます(日本語も可)。
- 数値Enum:
- C#:
enum Direction { Up, Down, Left, Right }
のように定義します。基底型はデフォルトでint
です。文字列を直接値とするEnumはC#にはありませんが、ToString()
メソッドや属性を使って似たようなことを実現します。
- TypeScript: C#の
- Union Types (特に文字列リテラルユニオン):
- TypeScript:
type Status = "active" | "inactive" | "pending";
のように、特定のリテラル値(多くは文字列ですが、数値や真偽値リテラルも可能)のいずれか一つであることを表す型を定義できます。例えばStatus
型の変数は"active"
,"inactive"
,"pending"
の3つの文字列のいずれかしか受け付けません。 - 「軽量」である理由: Enum(特に数値Enum)はJavaScriptにコンパイルされる際に、実行時に存在するオブジェクト(前方マッピングと逆引きマッピングのための構造)を生成します。一方、文字列リテラルユニオンのような型エイリアスは、純粋にコンパイル時の型チェックのためだけのもので、JavaScriptにコンパイルされると型情報は消え去ります(型消去)。そのため、実行時のコードには関連するオブジェクトが生成されず、ランタイムのオーバーヘッドがありません。
- C#: 文字列リテラルユニオンに直接相当する機能はありません。C#では、このようなケースでは文字列定数 (
const string
) や Enum を使うことが多いです。
- TypeScript:
課題内容:
-
新しいファイルの作成:
src
ディレクトリにenums-unions.ts
という新しいファイルを作成してください。 -
Enumと文字列リテラルユニオンの定義・使用: 作成したファイルに以下のようなコードを書いてみてください。
// src/enums-unions.ts export {}; // モジュール化のための空エクスポート // --- 1. 数値 Enum --- enum Direction { Up, // デフォルトで 0 Down, // 1 Left, // 2 Right // 3 } console.log("Numeric Enum Examples:"); let dir: Direction = Direction.Up; console.log(`Value of Direction.Up: ${dir}`); // 0 // 逆引きマッピング: 値から名前を取得できる (デバッグやロギングに便利) console.log(`Name for value 0: ${Direction[0]}`); // "Up" console.log(`Name for value of Left: ${Direction[Direction.Left]}`); // "Left" dir = Direction.Right; // Enum値の比較は、メンバー名を直接使うのが推奨 if (dir === Direction.Right) { console.log("Moving Right!"); } // if (dir === Direction[3]) { /* このような比較は非推奨 */ } // 初期値を設定することも可能 enum ResponseStatus { Success = 200, NotFound = 404, Error = 500 } console.log(`ResponseStatus.Success: ${ResponseStatus.Success}`); // 200 // --- 2. 文字列 Enum --- enum LogLevel { ERROR = "エラー発生", // 任意の文字列を値にできる WARNING = "警告あり", INFO = "通知" } console.log("\nString Enum Examples:"); let level: LogLevel = LogLevel.WARNING; console.log(`Current log level value: ${level}`); // "警告あり" // 文字列Enumには数値Enumのような自動的な逆引きマッピングは生成されない // console.log(LogLevel["エラー発生"]); // undefined function logMessage(message: string, logLevel: LogLevel): void { // logLevel は "エラー発生", "警告あり", "通知" のいずれかの文字列値 console.log(`[${logLevel}]: ${message}`); } logMessage("ディスク容量が少なくなっています。", LogLevel.WARNING); logMessage("重要な処理が失敗しました。", LogLevel.ERROR); // --- 3. 文字列リテラルユニオン型 --- // 特定の文字列リテラルのいずれかであることを示す型 type CardinalDirection = "North" | "East" | "South" | "West"; console.log("\nString Literal Union Examples:"); let move: CardinalDirection = "North"; console.log(`Moving: ${move}`); move = "West"; // move = "Up"; // Error: Type '"Up"' is not assignable to type 'CardinalDirection'. // コンパイル時にエラーとなり、許可された文字列以外は代入できない function moveCharacter(direction: CardinalDirection): void { // direction は "North", "East", "South", "West" のいずれかの文字列 console.log(`Character moves ${direction}.`); } moveCharacter("South"); // moveCharacter("Left"); // Error // --- Enum と文字列リテラルユニオンの使い分け考察 --- // **Enum:** // - JavaScriptにコンパイルされると、実行時に存在するオブジェクトが生成される。 // (例: Numeric Enum は { '0': 'Up', 'Up': 0, ... } のようなオブジェクトになる) // - このため、わずかながらランタイムのオーバーヘッド(メモリ、初期化時間)がある。 // - 数値との自動マッピングや逆引き(数値Enumの場合)が利用できる。 // - 伝統的な列挙型の振る舞いが必要な場合や、実行時にメンバーを反復処理したい場合に有用。 // // **文字列リテラルユニオン (例: type Status = "A" | "B"):** // - 純粋にコンパイル時の型チェックのための機能。 // - JavaScriptにコンパイルされると、型情報は消え去り、関連する実行時オブジェクトは生成されない。 // (例: `let status: Status = "A";` は `let status = "A";` というシンプルなJSになる) // - そのため「軽量」であり、ランタイムのオーバーヘッドがない。 // - 単純に「いくつかの決まった文字列のうちのどれか」という制約を設けたい場合に非常に便利で、 // TypeScriptではこの目的でEnumより好まれることが多い。 // - 文字列そのものを値として扱えるため、デバッグ時などに分かりやすい。
-
コンパイルと実行:
npx tsc
でコンパイルし、node dist/enums-unions.js
で実行して結果を確認してください。- エラーになる行のコメントを外して、型チェックが働くことを確認してください。
-
コミットとPush:
- 作成したファイルの変更をコミットし (例:
git commit -m "Update examples for Enums and String Literal Unions with clarifications"
)、GitHubにPushしてください。
- 作成したファイルの変更をコミットし (例: