本文 AI 產出,尚未審核

TypeScript 基本型別 – Enum(列舉)

簡介

在日常開發中,我們常會碰到「一組固定且有意義的值」——例如 HTTP 狀態碼、使用者角色、或是 UI 中的按鈕類型。直接以字串或數字硬寫在程式裡,既不易閱讀,也容易寫錯。TypeScript 提供的 Enum(列舉)正是為了把這類常數集合化、結構化,讓程式碼更具可讀性與可維護性。

Enum 同時結合了 型別安全(type‑safety)與 自動映射(auto‑mapping)的特性,讓開發者在編譯階段就能捕捉錯誤,並在執行階段得到直觀的值。掌握 Enum 的使用方式,是從「基本型別」過渡到「進階型別」的關鍵一步。


核心概念

1. Enum 的基本語法

在 TypeScript 中,使用 enum 關鍵字即可宣告一個列舉。預設情況下,Enum 會自動為每個成員分配遞增的數值(從 0 開始)。

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

此時 Direction.Up 的類型是 Direction,其值為 0。如果想要取得字串表示,只需要使用 反向映射(reverse mapping):

console.log(Direction[0]); // "Up"

小技巧:在需要顯式指定值時,可直接給成員賦值,後續成員會自動遞增。

enum StatusCode {
  Success = 200,
  NotFound = 404,
  ServerError = 500
}

2. 字串列舉(String Enum)

有時候數值型別的映射不夠直觀,尤其在除錯或日誌時。此時可以使用 字串列舉,每個成員必須手動賦值。

enum Role {
  Admin = "ADMIN",
  Editor = "EDITOR",
  Viewer = "VIEWER"
}

字串列舉不會產生反向映射,因為字串可能不具唯一性;但它在 JSON 序列化、API 交互 等情境下非常實用。

3. 雜湊列舉(混合型別)

TypeScript 允許在同一個 Enum 中混合數值與字串,但需注意 自動遞增 只適用於數值成員,且必須在字串成員之後。

enum Mixed {
  No = 0,
  Yes = "YES",   // 必須手動賦值
  Maybe = 2      // 會自動從前一個數值遞增
}

4. 常量列舉(Const Enum)

若關心程式碼體積,可使用 const enum。編譯時,TypeScript 會把列舉成員直接內聯(inline)成常量,省去列舉物件的產生。

const enum Color {
  Red,
  Green,
  Blue
}

// 編譯結果 (ES5)
let c = Color.Green; // => let c = 1;

注意:Const Enum 只能在 編譯階段 使用,不能在執行時取得列舉物件(如 Object.keys(Color) 會失效)。

5. 使用 Enum 作為型別

Enum 本身也是一種型別,常用於函式參數或變數的限定,提升型別安全。

function move(dir: Direction) {
  switch (dir) {
    case Direction.Up:    console.log("向上"); break;
    case Direction.Down:  console.log("向下"); break;
    case Direction.Left:  console.log("向左"); break;
    case Direction.Right: console.log("向右"); break;
  }
}

move(Direction.Left); // 正確
// move(5); // 編譯錯誤:Argument of type '5' is not assignable to parameter of type 'Direction'.

程式碼範例

範例 1️⃣:使用 Enum 管理 HTTP 狀態碼

enum HttpStatus {
  OK = 200,
  Created = 201,
  BadRequest = 400,
  Unauthorized = 401,
  NotFound = 404,
  InternalError = 500
}

function handleResponse(status: HttpStatus) {
  if (status === HttpStatus.OK) {
    console.log("請求成功");
  } else if (status >= HttpStatus.BadRequest && status < HttpStatus.InternalError) {
    console.warn("客戶端錯誤:", HttpStatus[status]); // 反向映射取得字串
  } else {
    console.error("伺服器錯誤");
  }
}

// 呼叫範例
handleResponse(HttpStatus.NotFound); // 警告: 客戶端錯誤: NotFound

重點:透過 Enum,錯誤代碼不會寫成「魔術數字」,而且 IDE 會提供自動完成。

範例 2️⃣:字串列舉搭配 Redux Action Types

enum ActionType {
  AddTodo = "ADD_TODO",
  ToggleTodo = "TOGGLE_TODO",
  DeleteTodo = "DELETE_TODO"
}

interface AddTodoAction {
  type: ActionType.AddTodo;
  payload: string;
}

interface ToggleTodoAction {
  type: ActionType.ToggleTodo;
  payload: number;
}

type TodoAction = AddTodoAction | ToggleTodoAction;

function reducer(state: string[], action: TodoAction) {
  switch (action.type) {
    case ActionType.AddTodo:
      return [...state, action.payload];
    case ActionType.ToggleTodo:
      // 省略實作
      return state;
    default:
      return state;
  }
}

使用 字串列舉,可確保 action.type 必定是已定義好的字串,減少拼寫錯誤。

範例 3️⃣:Const Enum 於效能敏感的圖形渲染

const enum ShaderType {
  Vertex,
  Fragment,
  Compute
}

function createShader(type: ShaderType, source: string) {
  // WebGL 需要數字常量
  const glShader = gl.createShader(type); // 編譯後會直接變成 gl.createShader(0) 等
  gl.shaderSource(glShader, source);
  gl.compileShader(glShader);
  return glShader;
}

// 使用
createShader(ShaderType.Vertex, vertexSrc);

在此情境下,ShaderType 只會在編譯階段展開,減少執行時的物件查找。

範例 4️⃣:混合型別列舉與資料庫映射

enum UserStatus {
  Active = 1,
  Inactive = 0,
  Banned = "BANNED"
}

// 假設從資料庫取得的狀態是數字或字串
function isUserActive(status: UserStatus): boolean {
  return status === UserStatus.Active;
}

// 測試
console.log(isUserActive(1)); // true
// console.log(isUserActive("BANNED")); // false,型別不符合

此例展示 混合型別 的彈性,同時保留型別安全。

範例 5️⃣:Enum 與 Type Guard 結合

enum ShapeKind {
  Circle,
  Square
}

interface Circle {
  kind: ShapeKind.Circle;
  radius: number;
}

interface Square {
  kind: ShapeKind.Square;
  side: number;
}

type Shape = Circle | Square;

function area(s: Shape): number {
  if (s.kind === ShapeKind.Circle) {
    // TypeScript 會自動推斷 s 為 Circle
    return Math.PI * s.radius ** 2;
  } else {
    // 此時 s 為 Square
    return s.side ** 2;
  }
}

利用 Enum 作為 辨識屬性(discriminant),可實現乾淨的 型別守衛(type guard)。


常見陷阱與最佳實踐

陷阱 說明 建議的做法
反向映射帶來的意外 數值 Enum 會自動產生 Enum[value] 形式的反向映射,若值重複會產生 覆寫。 避免重複值,或改用字串 Enum。
Const Enum 無法在執行時取得 const enum 會在編譯時內聯,執行時找不到物件本身。 只在純編譯階段使用,若需要在執行時檢查列舉成員,改用普通 Enum。
混用字串與數值 雖然語法允許,但會失去自動遞增的便利,且在大型程式中易造成混亂。 盡量保持同一類型(全數值或全字串),必要時拆成兩個列舉。
Enum 成員過度依賴硬編碼 直接把業務邏輯寫在 Enum 中(例如在成員上寫方法)會降低可維護性。 把行為抽離到 純函式 或 類別 中,Enum 僅負責資料。
未使用 as const 轉為字面量類型 有時候想要字串常量但又不想寫 Enum,直接使用 as const 更輕量。 考慮 字面量類型(type Role = keyof typeof RoleMap)作為替代方案。

最佳實踐:

  1. 優先使用字串 Enum 於 API 交互或日誌輸出,提升可讀性。
  2. 使用 const Enum 只在效能敏感且不需要反向映射的情境。
  3. 將 Enum 作為 discriminated union 的 kind 屬性,搭配型別守衛,建立可擴充的資料結構。
  4. 保持成員命名一致(大寫駝峰或全大寫),遵守團隊規範,減少拼寫錯誤。
  5. 在大型專案中,將 Enum 放在獨立檔案(例如 enums.ts),並使用 export/import 管理。

實際應用場景

  1. 狀態機(State Machine)
    用 Enum 定義所有可能的狀態(Idle, Loading, Success, Error),配合 switch 或 map 完成狀態轉換。

  2. 表單驗證規則
    把驗證類型(Required, Email, Phone)列舉化,讓驗證函式的參數只能是合法的規則。

  3. 國際化(i18n)鍵值
    Enum 代表所有翻譯鍵,如 enum LocaleKey { Welcome = "welcome", Logout = "logout" },可在程式中直接使用,避免硬寫字串。

  4. 權限管理
    以 Enum 定義角色或權限代號(Admin, User, Guest),配合 guard 中介層檢查使用者是否具備相應權限。

  5. 圖形與遊戲開發
    使用 const enum 表示渲染類型、事件代碼、鍵盤代碼等,減少執行時的查找開銷。


總結

Enum 是 TypeScript 中 將固定集合抽象化 的強大工具。透過 數值列舉、字串列舉、混合列舉 以及 const 列舉,開發者可以根據不同的需求選擇最合適的形式,從而提升程式碼的 可讀性、型別安全 與 執行效能。在實務開發中,善用 Enum 來管理狀態、錯誤代碼、角色權限等常見情境,能有效減少魔術字串與硬編碼的風險,並讓 IDE 提供更完整的自動完成與檢查。

關鍵要點

  • Enum = 常數集合 + 型別,使用前先思考是否需要數值或字串。
  • Const Enum 只在編譯時展開,適合效能敏感的場景。
  • 字串 Enum 更適合跨系統(API、日誌)傳遞。
  • 避免重複值、不要在執行時依賴反向映射,保持列舉的單一職責。

掌握以上概念與最佳實踐,你就能在 TypeScript 專案中靈活運用 Enum,寫出既安全又易維護的程式碼。祝你開發順利,Enum 玩得開心!