TypeScript 列舉型別
什麼是 Enum
Enum 被稱為「列舉」或「枚舉」,在原生的 JavaScript 並沒有這樣的概念,而是在 TypeScript 才新增的型別概念。以下內容將統一用列舉稱呼,它用來將性質類似的選項,用物件的鍵(Key)來匯聚成一個型別,很適合用在一組固定範圍的情境。例如:
- 訂單狀態:待處理、運送中、已完成
- 權限管理:管理者、可編輯者、僅能閱覽者
- 表單驗證:有效、無效、驗證中
如果只看說明的話,可能會有些抽象,所以我們以「角色權限管理」作為範例來做說明:
1 |
|
如上方範例所示,我們可以透過 enum
這樣的關鍵字來做定義,名稱依循命名慣例為首字大寫(UserRole
),而定義後可讓變數、參數做型別註記。這樣的方式就像為變數、參數設置了一個「選項清單」,所以得依循這個清單的選項,就像我們到餐廳用餐時,只能依循這家餐廳的菜單點菜,而不能點其他菜單上沒有的品項。
使用列舉能讓我們更有組織來管理性質類似的項目,而在擴充時也較為便利。
Enum 列舉的值
在上個段落中,有提到列舉是將性質類似的選項,用物件的鍵(Key)來匯聚成型別,而這些 Key 是有代表值的。如果列舉型別在宣告而沒有賦予值時,預設會是數字型列舉型別,而項目與值的對應會從 0 開始遞增(0、1、2)。如下方範例:
1 |
|
當沒有對項目賦予值時,TypeScript 會幫我們去對應從 0 開始遞增的數字。而我們也能主動對項目賦予值,不過在型別上只能是數字或字串,不能是這兩種型別以外的值。如下方三個範例所示:
1 |
|
1 |
|
1 |
|
特性與需注意
當列舉型別沒有被標示值時,會有這樣的特性:查看前一個項目的值並進行 +1
的遞增,而如果第一個項目沒有被賦予值時,則會代入數字 0。如下方範例,UserRole.Editor
會參考 UserRole.Admin
的值,然後進行 +1
的遞增,所以 UserRole.Editor
的值為 101,而 UserRole.Viewer
則以此類推為 102。
1 |
|
以下方這個範例來說,UserRole.Admin
是第一個項目且沒有值,所以會預設代入 0 這個數字,而 UserRole.Editor
則是被設置為 0,不過這樣並不會報錯,甚至兩者做 ===
比對時是相等的。因為 TypeScript 不會檢查這些項目的值是否不合理或重複。
1 |
|
接下來討論字串型列舉要注意的部分,如果第一個項目(前一個項目)的值為字串型別,則後續的項目都需要被定義值,不然會出現 Enum member must have initializer.
這樣的錯誤。以下方註解 ❌ 的範例來說,因為 UserRole.Editor
無法根據前一個 'Admin'
值做 +1
的遞增,所以我們會收到 TypeScript 的錯誤提醒。
1 |
|
這個需注意的段落,雖然實務上應該不會遇到(真的這樣寫可能會被亂棒毆打),但學習時有看到,還是筆記下來。
關於逆向映射性(反向查找)
在 TypeScript 使用列舉時,除了可以用項目(鍵)來查找值(UserRole.Admin
),我們也可以反向用值來找查找項目名稱(UserRole[0]
),這樣的相互映射稱為逆向映射性。
1 |
|
如下方範例所示,透過編譯後的結果,我們可以更了解這樣的特性是從何而來,以 UserRole[UserRole["Admin"] = 0] = "Admin";
這整段來說:
UserRole["Admin"] = 0
:這部分將UserRole
列舉的項目Admin
設置為 0,而這整體又是一個表達式,最終會回傳數值 0。UserRole[0] = "Admin"
:接著我們將上方UserRole["Admin"] = 0
整體回傳的 0 給代入,UserRole[0]
被設置為"Admin"
這個值。
1 |
|
不過在有些狀況,就不會有逆向映射的特性:
- 在字串型列舉型別的項目名稱與值不一樣時(以下範例說明)
- 使用常數列舉型別時(下一段將會說明)
在下方兩個字串型列舉型別的範例中,我們可以了解到:字串型列舉的編譯結果,是沒有雙向設置的部分。不過在項目名稱(鍵)跟值一樣時,因為可以將值反向作為屬性參照,所以仍然保有逆向映射的特性。
而以第二個範例來說,在項目名稱(鍵)跟值不同時,我們就無法反向來查找了。(UserRole["Abc"]
無法查找出 "Admin"
)
1 |
|
1 |
|
常數列舉型別
在列舉型別前加上 const
關鍵字宣告,就稱為常數列舉型別。而多加上 const
宣告時,編譯的內容就會有很大的不同。如下方範例所示:
1 |
|
從上方的編譯結果中,它並不像一般列舉,會產生物件與互相映射的結果,而是會把結果值直接代入使用的地方。這樣的優勢在於能讓編譯後的 JavaScript 檔案更小,但也意味著我們無法使用逆向映射。
而選擇用一般列舉或者常數列舉,則看實際的需求而定哩。
參考資料:
- 六角學院 TypeScript 30 天課程
- 書籍:<讓 TypeScript 成為你全端開發的 ACE!>
- 在大腦升級後,我想學 TypeScript 了 Day 11 - 列舉 Enum Type
Photo by Safar Safarov on Unsplash