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)作為替代方案。 |
最佳實踐:
- 優先使用字串 Enum 於 API 交互或日誌輸出,提升可讀性。
- 使用 const Enum 只在效能敏感且不需要反向映射的情境。
- 將 Enum 作為 discriminated union 的
kind屬性,搭配型別守衛,建立可擴充的資料結構。 - 保持成員命名一致(大寫駝峰或全大寫),遵守團隊規範,減少拼寫錯誤。
- 在大型專案中,將 Enum 放在獨立檔案(例如
enums.ts),並使用export/import管理。
實際應用場景
狀態機(State Machine)
用 Enum 定義所有可能的狀態(Idle,Loading,Success,Error),配合switch或map完成狀態轉換。表單驗證規則
把驗證類型(Required,Email,Phone)列舉化,讓驗證函式的參數只能是合法的規則。國際化(i18n)鍵值
Enum 代表所有翻譯鍵,如enum LocaleKey { Welcome = "welcome", Logout = "logout" },可在程式中直接使用,避免硬寫字串。權限管理
以 Enum 定義角色或權限代號(Admin,User,Guest),配合guard中介層檢查使用者是否具備相應權限。圖形與遊戲開發
使用const enum表示渲染類型、事件代碼、鍵盤代碼等,減少執行時的查找開銷。
總結
Enum 是 TypeScript 中 將固定集合抽象化 的強大工具。透過 數值列舉、字串列舉、混合列舉 以及 const 列舉,開發者可以根據不同的需求選擇最合適的形式,從而提升程式碼的 可讀性、型別安全 與 執行效能。在實務開發中,善用 Enum 來管理狀態、錯誤代碼、角色權限等常見情境,能有效減少魔術字串與硬編碼的風險,並讓 IDE 提供更完整的自動完成與檢查。
關鍵要點
- Enum = 常數集合 + 型別,使用前先思考是否需要數值或字串。
- Const Enum 只在編譯時展開,適合效能敏感的場景。
- 字串 Enum 更適合跨系統(API、日誌)傳遞。
- 避免重複值、不要在執行時依賴反向映射,保持列舉的單一職責。
掌握以上概念與最佳實踐,你就能在 TypeScript 專案中靈活運用 Enum,寫出既安全又易維護的程式碼。祝你開發順利,Enum 玩得開心!