TypeScript 列舉 (Enum)

列舉 (Enum) 是 TypeScript 提供的一種特殊型別,用來定義一組具名的常數。列舉可以讓程式碼更具可讀性,並且減少使用魔術數字 (magic numbers) 或魔術字串。

數字列舉 (Numeric Enum)

最常見的列舉類型,成員的值自動從 0 開始遞增:

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

let dir: Direction = Direction.Up;
console.log(dir); // 0

// 也可以用數字存取
console.log(Direction[0]); // "Up" (反向映射)

自訂起始值

enum Status {
  Pending = 1,
  Active, // 2
  Completed, // 3
  Cancelled, // 4
}

console.log(Status.Active); // 2

自訂每個值

enum HttpCode {
  OK = 200,
  Created = 201,
  BadRequest = 400,
  NotFound = 404,
  InternalError = 500,
}

function handleResponse(code: HttpCode) {
  if (code === HttpCode.OK) {
    console.log('成功!');
  }
}

handleResponse(HttpCode.OK);

字串列舉 (String Enum)

每個成員都必須用字串初始化:

enum Color {
  Red = 'RED',
  Green = 'GREEN',
  Blue = 'BLUE',
}

let color: Color = Color.Red;
console.log(color); // "RED"

// 字串列舉沒有反向映射
// console.log(Color["RED"]);  // undefined

字串列舉的優點:

  • 在執行時期有更明確的值,方便除錯
  • 輸出的值更具可讀性
  • 沒有反向映射,編譯後的程式碼更小
enum LogLevel {
  Error = 'ERROR',
  Warn = 'WARN',
  Info = 'INFO',
  Debug = 'DEBUG',
}

function log(level: LogLevel, message: string) {
  console.log(`[${level}] ${message}`);
}

log(LogLevel.Error, '發生錯誤!');
// 輸出:[ERROR] 發生錯誤!

異質列舉 (Heterogeneous Enum)

混合數字和字串成員 (不建議使用):

enum Mixed {
  No = 0,
  Yes = 'YES',
}
異質列舉雖然技術上可行,但會造成混淆,建議避免使用。

常數列舉 (Const Enum)

使用 const enum 可以獲得更好的效能,因為它在編譯時會被完全內聯:

const enum Direction {
  Up,
  Down,
  Left,
  Right,
}

let dir = Direction.Up;
// 編譯後:let dir = 0;

普通列舉編譯後會產生一個物件:

// 普通 enum 編譯後
var Direction;
(function (Direction) {
  Direction[(Direction['Up'] = 0)] = 'Up';
  Direction[(Direction['Down'] = 1)] = 'Down';
  Direction[(Direction['Left'] = 2)] = 'Left';
  Direction[(Direction['Right'] = 3)] = 'Right';
})(Direction || (Direction = {}));

常數列舉直接內聯值,沒有額外的物件。

const enum 不支援反向映射,也不能用於需要在執行時期動態存取列舉的情況。

計算成員與常數成員

列舉成員可以是常數或計算值:

enum FileAccess {
  // 常數成員
  None,
  Read = 1 << 1, // 2
  Write = 1 << 2, // 4
  ReadWrite = Read | Write, // 6

  // 計算成員
  G = '123'.length, // 3
}

列舉作為型別

列舉可以當作型別使用:

enum Status {
  Active,
  Inactive,
}

function setStatus(status: Status) {
  console.log(status);
}

setStatus(Status.Active); // OK
// setStatus(2);           // 錯誤 (strict 模式下)

聯合列舉與列舉成員型別

當所有列舉成員都是字面量時,每個成員都變成一個型別:

enum ShapeKind {
  Circle,
  Square,
}

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

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

// kind 必須是 ShapeKind.Circle
let c: Circle = {
  kind: ShapeKind.Circle,
  // kind: ShapeKind.Square,  // 錯誤
  radius: 10,
};

反向映射 (Reverse Mapping)

數字列舉支援反向映射,可以從值取得名稱:

enum Direction {
  Up,
  Down,
  Left,
  Right,
}

// 正向:名稱 → 值
console.log(Direction.Up); // 0

// 反向:值 → 名稱
console.log(Direction[0]); // "Up"
console.log(Direction[1]); // "Down"

列舉的替代方案

使用 const 物件

有時候使用 const 物件搭配 as const 是更輕量的選擇:

const Direction = {
  Up: 'UP',
  Down: 'DOWN',
  Left: 'LEFT',
  Right: 'RIGHT',
} as const;

// 取得值的聯合型別
type Direction = (typeof Direction)[keyof typeof Direction];
// type Direction = "UP" | "DOWN" | "LEFT" | "RIGHT"

function move(dir: Direction) {
  console.log(dir);
}

move(Direction.Up); // OK
move('UP'); // OK
// move("FORWARD");   // 錯誤

使用聯合型別

對於簡單的情況,直接使用字面量聯合型別:

type Direction = 'up' | 'down' | 'left' | 'right';

function move(dir: Direction) {
  console.log(dir);
}

move('up'); // OK
// move("forward");  // 錯誤

實際應用場景

API 狀態碼

enum ApiStatus {
  Success = 200,
  BadRequest = 400,
  Unauthorized = 401,
  Forbidden = 403,
  NotFound = 404,
  ServerError = 500,
}

使用者角色

enum UserRole {
  Admin = 'ADMIN',
  Editor = 'EDITOR',
  Viewer = 'VIEWER',
}

function checkPermission(role: UserRole) {
  switch (role) {
    case UserRole.Admin:
      return '完整權限';
    case UserRole.Editor:
      return '編輯權限';
    case UserRole.Viewer:
      return '唯讀權限';
  }
}

事件類型

enum EventType {
  Click = 'click',
  Hover = 'hover',
  Focus = 'focus',
  Blur = 'blur',
}

function addEventListener(event: EventType, handler: () => void) {
  document.addEventListener(event, handler);
}

總結

列舉類型說明使用場景
數字列舉值自動遞增需要反向映射、位元旗標
字串列舉明確的字串值API 值、日誌級別
常數列舉編譯時內聯效能要求高、不需反向映射