課題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) を直接使うのが最も推奨されます。
      • 文字列Enum: enum LogLevel { ERROR = "エラーメッセージ", INFO = "情報" } のように、メンバーに任意の文字列値を割り当てることができます(日本語も可)。
    • C#: enum Direction { Up, Down, Left, Right } のように定義します。基底型はデフォルトで int です。文字列を直接値とするEnumはC#にはありませんが、ToString() メソッドや属性を使って似たようなことを実現します。
  • Union Types (特に文字列リテラルユニオン):
    • TypeScript: type Status = "active" | "inactive" | "pending"; のように、特定のリテラル値(多くは文字列ですが、数値や真偽値リテラルも可能)のいずれか一つであることを表す型を定義できます。例えば Status 型の変数は "active", "inactive", "pending" の3つの文字列のいずれかしか受け付けません。
    • 「軽量」である理由: Enum(特に数値Enum)はJavaScriptにコンパイルされる際に、実行時に存在するオブジェクト(前方マッピングと逆引きマッピングのための構造)を生成します。一方、文字列リテラルユニオンのような型エイリアスは、純粋にコンパイル時の型チェックのためだけのもので、JavaScriptにコンパイルされると型情報は消え去ります(型消去)。そのため、実行時のコードには関連するオブジェクトが生成されず、ランタイムのオーバーヘッドがありません。
    • C#: 文字列リテラルユニオンに直接相当する機能はありません。C#では、このようなケースでは文字列定数 (const string) や Enum を使うことが多いです。

課題内容:

  1. 新しいファイルの作成: src ディレクトリに enums-unions.ts という新しいファイルを作成してください。

  2. 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より好まれることが多い。
    //   - 文字列そのものを値として扱えるため、デバッグ時などに分かりやすい。
    
  3. コンパイルと実行:

    • npx tsc でコンパイルし、node dist/enums-unions.js で実行して結果を確認してください。
    • エラーになる行のコメントを外して、型チェックが働くことを確認してください。
  4. コミットとPush:

    • 作成したファイルの変更をコミットし (例: git commit -m "Update examples for Enums and String Literal Unions with clarifications" )、GitHubにPushしてください。