🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

三個月前,我提交了一個我認為非常合理的拉取請求。我建立了一個新的UserRole枚舉來處理我們的權限系統。簡潔、型別安全、符合 TypeScript 規範。

資深工程師的評審結果只有一個: “請不要使用枚舉。”

我當時很困惑。枚舉在 TypeScript 手冊裡,每門課都會講到。主流程式碼庫都在用它們。枚舉有什麼問題嗎?

然後他向我展示了編譯後的 JavaScript 輸出。

那天下午我從我們的程式碼庫中刪除了每個枚舉。

本文解釋了為什麼 TypeScript 枚舉是該語言最容易被誤解的功能之一,以及為什麼你應該停止使用它們。


第一部分:枚舉錯覺

TypeScript 自稱是「具有型別語法的 JavaScript」。它的承諾很簡單:寫 TypeScript,獲得型別安全,編譯為乾淨的 JavaScript。

對於大多數 TypeScript 特性來說,確實如此。接口?被刪除了。類型註解?被刪除了。泛型?被刪除了。

枚舉?它們會變成真正的執行時程式碼。

這種根本的差異使得枚舉在 TypeScript 中成為一個異常現象,並且對於不了解編譯模型的開發人員來說是一個陷阱。

簡單的例子

讓我們從一些無辜的事情開始:

enum Status {
  Active = "ACTIVE",
  Inactive = "INACTIVE",
  Pending = "PENDING"
}

function getUserStatus(): Status {
  return Status.Active
}

看起來很簡潔,對吧?以下是實際交付給用戶的內容:

var Status;
(function (Status) {
  Status["Active"] = "ACTIVE";
  Status["Inactive"] = "INACTIVE";
  Status["Pending"] = "PENDING";
})(Status || (Status = {}));

function getUserStatus() {
  return Status.Active;
}

也就是說,5 行 TypeScript 程式碼對應9 行 JavaScript 程式碼

但等等——情況變得更糟了。


第 2 部分:數字枚舉的惡夢

字串枚舉很糟糕。數字枚舉更是一場災難。

enum Role {
  Admin,
  User,
  Guest
}

你可能會希望這段程式碼編譯起來很簡單。如const Role = { Admin: 0, User: 1, Guest: 2 }

以下是您實際得到的:

var Role;
(function (Role) {
  Role[Role["Admin"] = 0] = "Admin";
  Role[Role["User"] = 1] = "User";
  Role[Role["Guest"] = 2] = "Guest";
})(Role || (Role = {}));

這裡發生了什麼事?

TypeScript 正在建立反向映射。編譯後的物件如下所示:

{
  Admin: 0,
  User: 1,
  Guest: 2,
  0: "Admin",
  1: "User",
  2: "Guest"
}

這允許您執行以下操作: Role[0] // "Admin"

問題:您是否曾經需要過此功能?

在五年的專業 TypeScript 開發經驗中,我從來沒有需要透過枚舉值的數值來找出枚舉名稱。一次也沒有。

然而我已經將這段額外的程式碼投入生產數百次了。


第三部:Tree-Shaking 問題

Webpack、Rollup 和 Vite 等現代化打包工具都擁有先進的 tree-shaking 功能,能夠精確地刪除無用程式碼。

除非您使用枚舉。

問題

// types.ts
export enum Status {
  Active = "ACTIVE",
  Inactive = "INACTIVE",
  Pending = "PENDING",
  Archived = "ARCHIVED",
  Deleted = "DELETED"
}

// app.ts
import { Status } from './types'

const currentStatus = Status.Active

您想要的:只是捆綁包中的字串"ACTIVE"

您得到的是:整個Status枚舉物件加上 IIFE 包裝器。

枚舉無法進行 tree-shaking,因為它們是執行時構造的。即使你只使用一個值,你也會取得所有值。

在實際應用程式中將其乘以數十個枚舉,您將發送數千位元組的不必要程式碼。


第四部分:更好的選擇

那如果枚舉有問題,我們該用什麼來代替呢?

解決方案 1:使用 'as const' 的 Const 物件

const Status = {
  Active: "ACTIVE",
  Inactive: "INACTIVE",
  Pending: "PENDING"
} as const

編譯後的 JavaScript:

const Status = {
  Active: "ACTIVE",
  Inactive: "INACTIVE",
  Pending: "PENDING"
}

就是這樣。沒有 IIFE。沒有運轉時開銷。只是一個簡單的物件。

建立類型

type Status = typeof Status[keyof typeof Status]
// Expands to: type Status = "ACTIVE" | "INACTIVE" | "PENDING"

現在你有:

  • ✅ 值的執行時物件

  • ✅ 用於類型檢查的編譯時類型

  • ✅ 零編譯開銷

  • ✅ 可搖樹(如果你的打包工具支援的話)

用法

// Works exactly like enums:
function setStatus(status: Status) {
  console.log(status)
}

setStatus(Status.Active) // ✅ Valid
setStatus("ACTIVE")      // ✅ Valid (it's just a string)
setStatus("INVALID")     // ❌ Type error

第五部分:類型安全優勢

有趣的是: const 物件比枚舉提供更好的類型安全性。

列舉問題

enum Color {
  Red = 0,
  Blue = 1
}

enum Status {
  Inactive = 0,
  Active = 1
}

function setColor(color: Color) {
  console.log(`Color: ${color}`)
}

// This compiles successfully:
setColor(Status.Active) // No error!

為什麼?因為 TypeScript 枚舉使用結構化類型。 Color 和Status都是Color ,所以 TypeScript 認為它們相容。

這段程式碼編譯並發佈到生產環境。但它引發了一個 bug,需要幾個小時才能除錯。

物件解決方案

const Color = {
  Red: "RED",
  Blue: "BLUE"
} as const

const Status = {
  Inactive: "INACTIVE",
  Active: "ACTIVE"
} as const

type Color = typeof Color[keyof typeof Color]

function setColor(color: Color) {
  console.log(`Color: ${color}`)
}

// Type error:
setColor(Status.Active) // ❌ Type '"ACTIVE"' is not assignable to type '"RED" | "BLUE"'

const 物件方法使用文字類型,即精確的字串值。 TypeScript 在編譯時捕捉錯誤。

Const 物件提供比枚舉更嚴格的類型檢查。


第 6 部分:遷移路徑

相信了嗎?下面是如何遷移現有枚舉的方法。

步驟 1:辨識字串枚舉

這些是最容易遷移的:

// Before
enum Status {
  Active = "ACTIVE",
  Inactive = "INACTIVE"
}

// After
const Status = {
  Active: "ACTIVE",
  Inactive: "INACTIVE"
} as const

type Status = typeof Status[keyof typeof Status]

步驟 2:轉換數字枚舉

對於數字枚舉,您需要保留數字:

// Before
enum HttpStatus {
  OK = 200,
  NotFound = 404,
  ServerError = 500
}

// After
const HttpStatus = {
  OK: 200,
  NotFound: 404,
  ServerError: 500
} as const

type HttpStatus = typeof HttpStatus[keyof typeof HttpStatus]

步驟 3:更新使用狀況

好訊息?用法基本上保持不變:

// Both work identically:
const status1: Status = Status.Active
const status2: HttpStatus = HttpStatus.OK

// Pattern matching still works:
switch (status) {
  case Status.Active:
    // ...
  case Status.Inactive:
    // ...
}

步驟 4:處理邊緣狀況

如果您使用反向查找(很少見),則需要建立一個明確的反向映射:

const HttpStatus = {
  OK: 200,
  NotFound: 404
} as const

// Create reverse mapping only if needed:
const HttpStatusNames = {
  200: "OK",
  404: "NotFound"
} as const

HttpStatusNames[200] // "OK"

第七部分:唯一的例外

使用枚舉是否有正當理由?

可能:const 枚舉

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

const move = Direction.Up

編譯為:

const move = 0 /* Direction.Up */

常量枚舉在編譯時內。它們不會建立執行時物件。

然而:

  1. 它們不適用於isolatedModules (Babel、esbuild、SWC 所需)

  2. 它們已被棄用,取而代之的是preserveConstEnums

  3. 它們比僅僅使用物件更複雜

我的建議:即使是 const 枚舉,也只使用物件。越簡單越好。


第 8 部分:現實世界的影響

當我們將程式碼庫從枚舉遷移到 const 物件時,發生了以下情況:

遷移之前

  • 程式庫中的枚舉: 47

  • 捆綁包大小: 2.4 MB(最小化)

  • 捆綁包中與枚舉相關的程式碼: ~14 KB

遷移後

  • 程式碼庫中的枚舉: 0

  • 捆綁包大小: 2.388 MB(最小化)

  • 節省: 12 KB

“只有12KB?”

是的,但是:

  1. 12KB 的資料我們不需要傳送、解析或執行

  2. 類型安全性得到改善(我們在遷移過程中發現了 3 個錯誤)

  3. 程式碼變得更易讀(它只是 JavaScript)

  4. 新開發人員上手更快(TypeScript 怪癖更少)

開發者體驗改進

  1. 更快的編譯: TypeScript 不需要產生枚舉程式碼

  2. 更好的 IDE 效能:需要追蹤的執行時構造更少

  3. 更容易除錯:控制台日誌顯示實際值,而不是枚舉引用

  4. 更簡單的思考模型:少記住一個 TypeScript 特有的功能


第九部分:常見反對意見

“但是枚舉在 TypeScript 文件中!”

命名空間也是如此,它們也被視為遺留。 TypeScript 團隊已經承認枚舉是一個錯誤,但他們無法在不破壞變更的情況下刪除它們。

“我的整個程式碼庫都使用枚舉!”

遷移過程簡單直接,可以逐步完成。從新程式碼開始,在重構過程中遷移舊程式碼。

“枚舉更加明確!”

// Enum
enum Status { Active = "ACTIVE" }

// Object
const Status = { Active: "ACTIVE" } as const

差別很小。物件版本其實更符合 JavaScript 的慣用用法。

“我需要類型和值!”

使用 const 物件模式可以同時獲得這兩種效果:

const Status = { Active: "ACTIVE" } as const  // Runtime value
type Status = typeof Status[keyof typeof Status]  // Compile-time type

“JSON 序列化怎麼樣?”

無論如何,枚舉都會序列化為其底層值:

enum Status { Active = "ACTIVE" }
JSON.stringify({ status: Status.Active }) // {"status":"ACTIVE"}

相同於:

const Status = { Active: "ACTIVE" } as const
JSON.stringify({ status: Status.Active }) // {"status":"ACTIVE"}

沒有差別。


第十部分:哲學觀點

TypeScript 的座右銘是「可擴展的 JavaScript」。最好的 TypeScript 程式碼是看起來像 JavaScript 但帶有類型註解的程式碼。

枚舉違反了這項原則。它們是一種 TypeScript 獨有的構造,會產生執行時間程式碼,且其行為與 JavaScript 中的任何內容都不同。

如有疑問,請優先使用具有 TypeScript 類型的 JavaScript 慣用語,而不是 TypeScript 特定的功能。

好的 TypeScript:

const Status = { Active: "ACTIVE" } as const
type Status = typeof Status[keyof typeof Status]

這是具有 TypeScript 類型的 JavaScript(一個物件)。它可擴展。它很熟悉。它在任何地方都能工作。

可疑的 TypeScript:

enum Status { Active = "ACTIVE" }

這是 TypeScript 特定的語法,會產生意外的執行時間程式碼。


結論:做出改變

TypeScript 枚舉在 2012 年似乎是個好主意。到 2025 年,我們將有更好的選擇。

反對枚舉的情況:

  • ❌ 產生意外的執行時間程式碼

  • ❌ 不要搖晃樹

  • ❌ 建立沒人使用的反向映射

  • ❌ 型別安全性比字面量型弱

  • ❌ TypeScript 特定的語法

const 物件的情況:

  • ✅ 零運轉時開銷

  • ✅ 可搖樹

  • ✅ 僅 JavaScript

  • ✅ 更強的型別安全性

  • ✅ 隨處可用

下次使用枚舉時,請改用 const 物件。

你的包包會更小。你的類型會更嚴格。你的程式碼會更清晰。

停止使用枚舉。開始使用物件。


快速參考指南

字串枚舉遷移

// ❌ Old way
enum Status {
  Active = "ACTIVE",
  Inactive = "INACTIVE"
}

// ✅ New way
const Status = {
  Active: "ACTIVE",
  Inactive: "INACTIVE"
} as const

type Status = typeof Status[keyof typeof Status]

數位枚舉遷移

// ❌ Old way
enum Priority {
  Low = 1,
  Medium = 2,
  High = 3
}

// ✅ New way
const Priority = {
  Low: 1,
  Medium: 2,
  High: 3
} as const

type Priority = typeof Priority[keyof typeof Priority]

可重複使用性的輔助類型

// Create a reusable type helper
type ValueOf<T> = T[keyof T]

const Status = {
  Active: "ACTIVE",
  Inactive: "INACTIVE"
} as const

type Status = ValueOf<typeof Status>

進一步閱讀


關於我

我是資深 TypeScript 開發者,擁有 5 年以上的生產級應用程式開發經驗。我透過向數百萬用戶推送不必要的枚舉程式碼,學到了這一慘痛教訓。現在,我將分享我的經驗,希望您避免重蹈覆轍。

如果您覺得這有幫助,請考慮與您的團隊分享。理解這一點的開發人員越多,我們交付的程式碼就越好。


原文出處:https://dev.to/elvissautet/the-code-review-that-changed-everything-1dp2


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝28   💬6   ❤️8
679
🥈
我愛JS
📝1   💬11   ❤️1
78
🥉
御魂
💬1  
4
#4
2
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付