iT邦幫忙

2023 iThome 鐵人賽

DAY 8
0

枚舉是 自定義一組具名常數值 的特殊型別,每個常數值都有一個名稱和一個關聯的數值,主要用於取值限定在一定的範圍內。在 TypeScript 中,我們使用 enum 關鍵字來定義枚舉。

那為什麼要定義枚舉值呢?因為後端工程師在開狀態的時候,有時會用 0、1、2 之類來分類狀態,如果我們或後端工程師都沒寫註解來說明數字代表什麼意思時,其他工程師可能短時間內會無法理解而浪費開發時間,把枚舉值定義清楚後,這樣就一目瞭然了。

在 TypeScript 裡自定義型別,通常開頭首字母 大寫,定義常數值 (枚舉成員) 時如果沒有賦值,第一個參數會默認為 0,之後會逐步遞增 1 (後面如果沒有賦值,會自動依據前面的值自動 +1)。

看以下範例:

enum Status {
  Error, // 0
  Success, // 1
  Warning, // 2
  Info, // 3
}

我們定義一個 Status 的枚舉值,其中包含了四個成員:ErrorSuccessWarningInfo。在預設情況下,這些成員的數值會從 0 開始遞增,分別是 0、1、23

https://ithelp.ithome.com.tw/upload/images/20230908/20141250yyxzzXBKyJ.png

接著我們嘗試修改枚舉成員 Success 的值變為 5

enum Status {
  Error, // 0
  Success = 5, // 5
  Warning, // 6
  Info, // 7
}

可以看到數值變成 05、6、7

https://ithelp.ithome.com.tw/upload/images/20230908/20141250dnyginEFEd.png

當然我們也可以自訂枚舉成員的數值:

enum Status {
  Error = "ERROR",
  Success = "SUCCESS",
  Warning = "WARNING",
  Info = "INFO",
}

https://ithelp.ithome.com.tw/upload/images/20230908/20141250VGhySiiNkV.png

不過要特別注意的是 賦值不要出現重複,否則會進行覆蓋。

我們又再次修改把枚舉成員 Success 的值變為 0,看會發生什麼:

enum Status {
  Error, // 0
  Success = 0, // 0
  Warning, // 1
  Info, // 2
}

console.log(Status.Error); // 輸出: 0
console.log(Status[0]); // 輸出: Success

可以看到原本的 0: "Error" 已經被 Success 覆蓋了,變成 0: "Success"

https://ithelp.ithome.com.tw/upload/images/20230908/20141250dk5ZIZFuy7.png

我們來個小練習:如果要定義一個 Weekday 的枚舉值來代表星期,星期日要為 7,星期一為 1,以此類推,那該怎麼定義呢?

enum Weekday = {
  Sun = 7, // 7
  Mon = 1, // 1
  Tue, // 2
  Wed, // 3
  Thu, // 4
  Fri, // 5
  Sat // 6
}

既然我們都會定義枚舉值了,那該如何使用枚舉值呢?

假設我們要做一個遊戲,方向有上下左右 (0、1、2、3),而取得使用者的方向為 0,該如何讓程式知道使用者往哪個方向走了呢?

看以下範例:

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

let userDirection = 0; // 假設取得的值為 0
let userWay: string;

if (userDirection === Direction.Up) {
  userWay = "威爾豬往上走";
} else if (userDirection === Direction.Down) {
  userWay = "威爾豬往下走";
} else if (userDirection === Direction.Left) {
  userWay = "威爾豬往左走";
} else {
  userWay = "威爾豬往右走";
}

console.log(userWay); // 輸出: 威爾豬往上走

這次我們修改使用者的方向為 2,並使用 Switch Case 的方式改寫:

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

let userDirection = 2; // 假設取得的值為 2
let userWay: string;

switch (userDirection) {
  case Direction.Up:
    userWay = "威爾豬往上走";
    break;
  case Direction.Down:
    userWay = "威爾豬往下走";
    break;
  case Direction.Left:
    userWay = "威爾豬往左走";
    break;
  default:
    userWay = "威爾豬往右走";
    break;
}

console.log(userWay); // 輸出: 威爾豬往左走

我們再看另一個例子,如果要依對象的性別來打招呼,該怎麼做呢?

enum Gender {
  Male,
  Female,
  Other,
}

const greetUser = (name: string, gender: number): string => {
  let greeting: string;

  if (gender === Gender.Male) {
    greeting = `哈囉,${name}帥哥`;
  } else if (gender === Gender.Female) {
    greeting = `哈囉,${name}美女`;
  } else {
    greeting = `哈囉,尊貴的${name}`;
  }

  console.log(greeting);
  return greeting;
};

greetUser("威爾豬", 0); // 輸出: 哈囉,威爾豬帥哥
greetUser("威爾羊", 1); // 輸出: 哈囉,威爾羊美女
greetUser("威爾牛", 2); // 輸出: 哈囉,尊貴的威爾牛

如果我們需要列出枚舉成員的話,我們可以使用 for...in。但需要注意的是,這樣不僅會列出枚舉成員,還會列出 enum 的其他屬性。

enum Status {
  Error,
  Success,
  Warning,
  Info,
}

for (let key in Status) {
  console.log(key); // 輸出: 0 1 2 3 Error Success Warning Info
}

如果只想列出枚舉成員,可以使用以下方式:

enum Status {
  Error,
  Success,
  Warning,
  Info,
}

for (let key in Status) {
  if (typeof Status[key] === "number") {
    console.log(key); // 輸出: Error Success Warning Info
  }
}

複合枚舉

這是在 TypeScript 中 結合兩個或多個枚舉值來創建更複雜的結構,這樣可以通過將多個枚舉值合併到一個新的枚舉值中,不過威爾豬很少會這樣做。

假設我們有兩個枚舉值 Phone 和 Color,然後我們將 Phone 和 Color 的枚舉成員合併到一個新的枚舉值 PhoneAndColor 中,以實現結合的效果。看以下範例:

// 合併枚舉

enum Phone {
  Apple,
  Samsung,
  Mi,
}

enum Color {
  Gold,
  Yellow,
  White,
}

enum PhoneAndColor {
  Apple = Phone.Apple, // 0
  Samsung = Phone.Samsung, // 1
  Mi = Phone.Mi, // 2
  Gold = Color.Gold, // 0
  Yellow = Color.Yellow, // 1
  White = Color.White, // 2
}

const selectPhone: PhoneAndColor = PhoneAndColor.Apple;
const selectColor: PhoneAndColor = PhoneAndColor.White;

console.log(PhoneAndColor);
console.log(selectPhone); // 輸出: 0
console.log(selectColor); // 輸出: 2
console.log(PhoneAndColor[1]) // 輸出: Yellow

需注意的是,Color 會覆蓋掉 Phone:0: "Gold"、1: "Yellow"、2: "White"

https://ithelp.ithome.com.tw/upload/images/20230908/20141250LUjMI1ygPg.png

常數枚舉

我們也可以使用 const enum,在編譯時將枚舉值直接轉換為常數,這樣定義就 不會在 .js 裡被編譯出來,從而減少生成的程式碼,我們可以看看相同程式碼編譯出來的 JavaScript 文件:

// 未使用 const

enum Gender {
  Male,
  Female,
  Other,
}

const gender = Gender.Female;
console.log(gender); // 輸出: 1

https://ithelp.ithome.com.tw/upload/images/20230908/20141250qfK3kmezEG.png

// 使用 const

const enum Gender {
  Male,
  Female,
  Other,
}

const gender = Gender.Female;
console.log(gender); // 輸出: 1

https://ithelp.ithome.com.tw/upload/images/20230908/201412501DaswNREPj.png

這對於優化文件的大小很有幫助,在某些情況下也可以提高性能,但同時也有一些缺點和限制:

  • 無法動態生成枚舉成員: 常數枚舉的成員在編譯時就已經被解析為字面值,表示我們無法基於某些條件來動態添加或刪除枚舉成員。

  • 無法進行反向查找: 常數枚舉 無法透過數值來查找對應的枚舉成員名稱,這在某些情況下可能不太方便使用。

  • 不能被導出: 常數枚舉 不能被導出到外部模組來重複使用

總而言之,常數枚舉在我們需要優化性能,並確保枚舉值在編譯時被完全替換為數值時非常有用,然而它的限制可能在某些情況下變得不太方便。因此,我們要仔細考慮需求,並選擇適合的枚舉值類型。

透過定義枚舉,我們可以更清楚地表示一組相關的常數值 (枚舉成員),無論是用於表示狀態、選項或其它具有固定含義的值,枚舉值都有助於使程式碼更具結構性和可理解性,減少我們需要去翻文件或詢問的時間。


上一篇
陣列的基本方法
下一篇
任意型別 VS. 未知型別 (any VS. unknown)
系列文
用不到 30 天學會基本 TypeScript30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言