TypeScript | 善用 Enum 提高程式的可讀性 - 基本用法 feat. JavaScript

神Q超人
Enjoy life enjoy coding
8 min readJul 7, 2019
使用 Enum 提高程式的可讀性

前言

Hi !大家好,我是神 Q 超人。昨天跑去看了 Toy story 4 ,整場電影勾起超多回憶,讓眼眶不爭氣泛淚了四次 😭 ,然後一整晚都沈浸在那個劇情中,到今天才默默打開電腦完成這篇文章。

本篇主要是解釋 Enum 的基本使用方式,以及它能夠為程式帶來什麼好處,雖然好處的部分已在標題破梗,但搭配一些例子來看應該也是不錯 😂 。

Enum

Enum 是在 TypeScript 中增加的新語法,也被稱做「列舉」或「枚舉」,實務面會用它來管理多個同系列的常數(不可修改的變數),做為狀態的判斷所使用。

在 Web 中比較常見的狀態判斷,是在處理請求時,要針對不同的 Response status codes (狀態代碼)做對應的處理:

但因為 Response status codes 是大家都定義好的,所以比較沒什麼爭議,看起來這麼寫也很正常,但是如果今天是團隊裡的後端在 Server 處理資料發生錯誤時,定義了一些代碼,來告訴前端說,這個代碼代表什麼錯誤,那個又是什麼,那上方的 function 可能會變成這樣子:

嗯…別說是剛接手的人,這太強人所難了對吧?就假如這是你一個月前完成的 Function ,現在除了到處翻翻文件、或是請後端再來討論一下外,還有其他方式可以知道它們各自代表什麼錯誤嗎?

沒有!

對,本篇第一個被放大字的重點就是「沒有」,但是如果善用 Enum ,就可以避免上述發生的情況。

基本用法

先來看看 Enum 該怎麼定義,其實它和 Object 的用法很像:

enum requestStatusCodes {
error,
success,
}

不需要在內容與名稱之間加上等於,直接在大括號內敘述該 Enum 中具有哪些變數,但雖說是變數,其實說常數更恰當,因為在 Enum 中的值是不可修改的,因此也不必擔心這些定義好的規則,會在程式執行的過程中改變,導致執行錯誤。

而既然 Enum 是定義同系列常數,那這些常數應該都能保管特定的 Value ,是的,在 Enum 中的每個常數,都可以經由 = 指定 Value 。

但如果是像上方的 requestStatusCodes ,沒有為 errorsuccess 指定 Value 也不會出錯,因為 TypeScript 會從 0 開始,用流水號為自動定義 Value ,所以上方的 requestStatusCodes 會與下方得到相同的結果:

enum requestStatusCodes {
error = 0,
success = 1,
}
console.log(requestStatusCodes.error) // 0
console.log(requestStatusCodes.success) // 1

除了數字外,也可以為它定義字串:

enum requestWrongCodes {
missingParameter = 'A',
wrongParameter = 'B',
invalidToken = 'C',
}
console.log(requestWrongCodes.wrongParameter) // 'B'

當然也可以在一個 enum 中設定不同的類型,但這樣子一點意義都沒有:

enum requestStatusCodes {
error = 0,
success = 'OK',
}

了解基本的 Enum 怎麼定義後,就來改寫上方的 handleResponseStatushandleWrongStatus ,讓它們在語義上能夠更明確吧!

首先用 Enum 定義兩者的狀態敘述:

再來修改 handleResponseStatushandleWrongStatus 內的 Switch 判斷:

修改後的程式碼就變得直觀多了,因為 Status Codes 都被放到 Enum 中統一管理,所以就能用代表那些 Status Codes 的常數名稱來代表它們,之後不管過了多久,再回來看都可以明確的知道這裡再做什麼,甚至連註解或文件都可以省下來,因為程式碼就是最好的文件。

這讓我想到 Kent Beck 在 Refactoring: Improving the Design of Existing Code 裡面說:

任何傻瓜都可以寫出電腦看得懂的程式,但好的程式設計師會寫出人也看得懂的。

在讓程式變好的一千零一招中,善用 Enum 絕對是不可或缺的,但就算沒使用 TypeScript 也別灰心,因為 TypeScript 最終會被轉換為 JavaScript ,那來看看如何直接用 JavaScript 實現 Enum 吧!

用原生 JavaScript 實現 Enum

在文章一開始有提到說, Enum 很像 Object ,但其實 Enum 被編譯過後,就真的是 Object ,例如上方的 requestStatusCodes 在經過編譯後會變成:

請原諒那些紅色底線,因為想讓大家感受原汁原味,就沒另外修正 ESLint 了

可以發現 Enum 就是編譯成 Key 和 Value 反向對應的物件,這樣看起來非常簡單,為了方便使用,下方把它的編譯方式寫成成一個 Function :

雖然得到的結果相同,但是這麼做就喪失了 Enum 中最可貴的「常數」特色,一旦不能讓它變成不可修改,那就有可能會在程式裡不經意地動到它,導致執行結果可能出錯,於是可以在最後利用 Object.freeze() ,讓外部操作無法新增、刪除或重新定義任何 Property :

如此一來就能簡單在 JavaScript 中實現 Enum 啦!但是如果要在替 Enum 中的每個敘述做更詳細的資料內容,也可以參考 這篇文章 的作法。

特別用法 const Enum

從上方的 JavaScript 中,可以看到 Enum 編譯過後會變成 Key 和 Value 互相對應的 Object ,也就是說不管是用 Key 還是Value 都可以取出對應的值,

但是如果使用 const 宣告 Enum ,編譯過就不會產生 Object。

直接來看例子,假設我將 responseStateconst 重新宣告,且也是以 handleResponseStatus 使用該 Enum 做判斷:

看起來一切正常,不過將目光移到編譯後的 JavaScript 時,會發現 Enum 並沒有產生 Object ,而是直接使用以 const 宣告在 Enum 中的值:

使用 const 宣告的 Enum 不會產生 Object ,而是直接填值

const 宣告 Enum 有幾個好處:

  1. 假設使用的 Enum 非常多,那在執行時就會不停地使用 IIFE 產生 Object 將 Key 和 Value 綁定到 Object,會造成一些效能上的耗損,也會增加內存,但是 const 並不會產生 Object ,也就不會有以上的問題。
  2. 就算 Enum 不多,再判斷時也需要一直「從 Object 中找出對應的值」,而如果是用 const 宣告 Enum ,在編譯成 JS 時就將宣告的值直接放入判斷式。

不過這麼一來也要注意,就沒辦法從 Enum 中反向取出值了,因為它並不會產生物件:

const enum responseStatus {
error = 400,
success = 200,
}
// 會出錯,因為已經沒有物件可以查找了
console.log(responseStatus[400])
// 但這個還是會對,因為編譯的時候會直接填值
console.log(responseStatus.error)
// 編譯後:
// console.log(400)

本文利用了一些平常會見到的例子講解 Enum 的使用方式,這樣比較容易了解在什麼樣的情況下會需要它,最後雖然只是抄下 TypeScript 的語法,不過也是客串了 JavaScript 的方式實現 Enum ,如果文章裡有任何錯誤或講解不清楚的部分,再麻煩各位留言告訴我,感激不盡 🙇 。

補充上使用 const 宣告 Enum 的差別,感謝 caasih 留言提醒 🙌 。

參考文章

  1. https://www.typescriptlang.org/docs/handbook/enums.html
  2. https://ithelp.ithome.com.tw/articles/10190758
  3. https://stijndewitt.com/2014/01/26/enums-in-javascript/
  4. https://netbasal.com/a-smaller-bundle-with-const-enums-in-typescript-b7f786227e5e

--

--