TypeScript | 善用 Enum 提高程式的可讀性 - 基本用法 feat. JavaScript
前言
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
,沒有為 error
或 success
指定 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 怎麼定義後,就來改寫上方的 handleResponseStatus
和 handleWrongStatus
,讓它們在語義上能夠更明確吧!
首先用 Enum 定義兩者的狀態敘述:
再來修改 handleResponseStatus
和 handleWrongStatus
內的 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
在經過編譯後會變成:
可以發現 Enum 就是編譯成 Key 和 Value 反向對應的物件,這樣看起來非常簡單,為了方便使用,下方把它的編譯方式寫成成一個 Function :
雖然得到的結果相同,但是這麼做就喪失了 Enum 中最可貴的「常數」特色,一旦不能讓它變成不可修改,那就有可能會在程式裡不經意地動到它,導致執行結果可能出錯,於是可以在最後利用 Object.freeze()
,讓外部操作無法新增、刪除或重新定義任何 Property :
如此一來就能簡單在 JavaScript 中實現 Enum 啦!但是如果要在替 Enum 中的每個敘述做更詳細的資料內容,也可以參考 這篇文章 的作法。
特別用法 const Enum
從上方的 JavaScript 中,可以看到 Enum 編譯過後會變成 Key 和 Value 互相對應的 Object ,也就是說不管是用 Key 還是Value 都可以取出對應的值,
但是如果使用
const
宣告 Enum ,編譯過就不會產生 Object。
直接來看例子,假設我將 responseState
用 const
重新宣告,且也是以 handleResponseStatus
使用該 Enum 做判斷:
看起來一切正常,不過將目光移到編譯後的 JavaScript 時,會發現 Enum 並沒有產生 Object ,而是直接使用以 const
宣告在 Enum 中的值:
用 const
宣告 Enum 有幾個好處:
- 假設使用的 Enum 非常多,那在執行時就會不停地使用 IIFE 產生 Object 將 Key 和 Value 綁定到 Object,會造成一些效能上的耗損,也會增加內存,但是
const
並不會產生 Object ,也就不會有以上的問題。 - 就算 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 留言提醒 🙌 。
參考文章