本文 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 unionkind 屬性,搭配型別守衛,建立可擴充的資料結構。
  4. 保持成員命名一致(大寫駝峰或全大寫),遵守團隊規範,減少拼寫錯誤。
  5. 在大型專案中,將 Enum 放在獨立檔案(例如 enums.ts),並使用 export/import 管理。

實際應用場景

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

  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 玩得開心!