
英文 | https://www.digitalocean.com/community/tutorials/how-to-use-enums-in-typescript
翻譯 | 楊小愛
介紹
在 TypeScript 中,枚舉 或枚舉類型是具有一組常量值的常量長度的資料結構。這些常量值中的每一個都稱為枚舉的成員。在設定隻能是一定數量的可能值的屬性或值時,枚舉很有用。
一個常見的例子是一副撲克牌中單張牌的花色值。抽出的每張牌要麼是梅花、方塊、紅心或黑桃;除了這四個之外,沒有可能的花色值,并且這些可能的值不太可能改變。是以,枚舉将是一種有效且清晰的方式來描述一張牌的可能花色。
盡管 TypeScript 的大多數功能都可用于在編譯期間引發錯誤,但枚舉也可用作可以儲存代碼常量的資料結構。
TypeScript 在編譯器發出的最終代碼中将枚舉轉換為 JavaScript 對象。正因為如此,我們可以使用枚舉使代碼庫更具可讀性,因為我們可以将多個常量值分組在同一個資料結構中,同時,也使代碼比僅使用不同的 const 變量更具有類型安全性。
本教程将解釋用于建立枚舉類型的文法、TypeScript 編譯器在背景建立的 JavaScript 代碼、如何提取枚舉對象類型,以及遊戲開發中涉及位标志的枚舉用例。
準備工作
要遵循本教程,我們将需要:
一個環境,我們可以在其中執行 TypeScript 程式以跟随示例。要在本地計算機上進行設定,我們将需要以下内容:
為了運作處理 TypeScript 相關包的開發環境,同時,安裝了 Node 和 npm(或 yarn)。本教程使用 Node.js 版本 14.3.0 和 npm 版本 6.14.5 進行了測試。要在 macOS 或 Ubuntu 18.04 上安裝,請按照如何在 macOS 上安裝 Node.js 和建立本地開發環境或如何在 Ubuntu 18.04 上安裝 Node.js 的使用 PPA 安裝部分中的步驟進行操作。如果我們使用的是适用于 Linux 的 Windows 子系統 (WSL),這也适用。
此外,我們需要在機器上安裝 TypeScript 編譯器 (tsc)。為此,請參閱官方 TypeScript 網站。
如果我們不想在本地機器上建立 TypeScript 環境,我們可以使用官方的 TypeScript Playground 來跟随。
我們将需要足夠的 JavaScript 知識,尤其是 ES6+ 文法,例如解構、剩餘參數和導入/導出。
本教程将參考支援 TypeScript 并顯示内聯錯誤的文本編輯器的各個方面。這不是使用 TypeScript 所必需的,但确實可以更多地利用 TypeScript 功能。為了獲得這些好處,我們可以使用像 Visual Studio Code 這樣的文本編輯器,它完全支援開箱即用的 TypeScript。我們也可以在 TypeScript Playground 中嘗試這些好處。
本教程中顯示的所有示例都是使用 TypeScript 4.2.3 版建立的。
在 TypeScript 中建立枚舉
在本節中,我們将運作一個同時聲明數字枚舉和字元串枚舉的示例。
TypeScript 中的枚舉通常用于表示給定值的确定數量的選項。這些資料排列在一組鍵/值對中。雖然鍵必須是字元串,與一般的 JavaScript 對象一樣,枚舉成員的值通常是自動遞增的數字,主要用于區分一個成員和另一個成員。隻有數字值的枚舉稱為數字枚舉。
要建立數字枚舉,請使用 enum 關鍵字,後跟enum 的名稱。然後建立一個花括号 ({}) 塊,我們将在其中指定enum成員,如下所示:
enum CardinalDirection {
North = 1,
East,
South,
West,
};
在此示例中,我們正在建立一個名為 CardinalDirection 的枚舉,它有一個代表每個基本方向的成員。
我們使用數字 1 作為 CardinalDirection 枚舉的第一個成員的值。這将數字 1 指定為 North 的值。但是,我們沒有将值配置設定給其他成員。這是因為 TypeScript 自動将剩餘成員設定為前一個成員的值加一。CardinalDirection.East 的值為 2,CardinalDirection.South 的值為 3,CardinalDirection.West 的值為 4。
此行為僅适用于每個成員隻有數字值的數字枚舉。
我們也可以完全忽略設定枚舉成員的值:
enum CardinalDirection {
North,
East,
South,
West,
};
在這種情況下,TypeScript 會将第一個成員設定為 0,然後,根據該成員自動設定其他成員,每個成員遞增 1。這将産生與以下相同的代碼:
enum CardinalDirection {
North= 0,
East= 1,
South= 2,
West= 3,
};
TypeScript 編譯器預設為枚舉成員配置設定數字,但我們可以覆寫它以生成字元串枚舉。這些是每個成員都有字元串值的枚舉;當值需要具有某種人類可讀的含義時,這些很有用,例如,如果我們稍後需要讀取日志或錯誤消息中的值。
我們可以使用以下代碼将枚舉成員聲明為具有字元串值:
enum CardinalDirection {
North = 'N',
East = 'E',
South = 'S',
West = 'W'
}
現在每個方向都有一個字母值,訓示它們綁定到哪個方向。
通過涵蓋聲明文法,我們現在可以檢視底層 JavaScript 以了解有關枚舉行為方式的更多資訊,包括鍵/值對的雙向特性。
雙向枚舉成員
在 TypeScript 編譯後,枚舉被轉換為 JavaScript 對象。但是,枚舉有一些特性可以将它們與對象區分開來。它們為存儲常量成員提供了比傳統 JavaScript 對象更穩定的資料結構,并且還為枚舉成員提供了雙向引用。為了展示它是如何工作的,本節将向我們展示 TypeScript 如何在最終代碼中編譯枚舉。
擷取我們在上一節中建立的字元串枚舉:
enum CardinalDirection {
North = 'N',
East = 'E',
South = 'S',
West = 'W',
};
當使用 TypeScript 編譯器編譯為 JavaScript 時,這将變成以下代碼:
"use strict";
var CardinalDirection;
(function (CardinalDirection) {
CardinalDirection["North"] = "N";
CardinalDirection["East"] = "E";
CardinalDirection["South"] = "S";
CardinalDirection["West"] = "W";
})(CardinalDirection || (CardinalDirection = {}));
在這段代碼中,“use strict”字元串啟動了嚴格模式,這是一種更嚴格的 JavaScript 版本。之後,TypeScript 建立一個沒有值的變量 CardinalDirection。然後,代碼包含一個立即調用的函數表達式 (IIFE),它将 CardinalDirection 變量作為參數,同時,還将其值設定為空對象 ({})(如果尚未設定)。
在函數内部,一旦将 CardinalDirection 設定為空對象,代碼就會為該對象配置設定多個屬性:
"use strict";
var CardinalDirection;
(function (CardinalDirection) {
CardinalDirection["North"] = "N";
CardinalDirection["East"] = "E";
CardinalDirection["South"] = "S";
CardinalDirection["West"] = "W";
})(CardinalDirection || (CardinalDirection = {}));
請注意,每個屬性都是原始枚舉的一個成員,其值設定為枚舉的成員值。
對于字元串枚舉,這是過程的結束。但是,接下來,我們将嘗試使用上一節中的數字枚舉進行相同的操作:
enum CardinalDirection {
North = 1,
East,
South,
West,
};
這将産生以下代碼,并添加了突出顯示的部分:
"use strict";
var CardinalDirection;
(function (CardinalDirection) {
CardinalDirection[CardinalDirection["North"] = 1] = "North";
CardinalDirection[CardinalDirection["East"] = 2] = "East";
CardinalDirection[CardinalDirection["South"] = 3] = "South";
CardinalDirection[CardinalDirection["West"] = 4] = "West";
})(CardinalDirection || (CardinalDirection = {}));
除了枚舉的每個成員都成為對象的屬性 (CardinalDirection["North"] = 1]) 之外,枚舉還為每個數字建立一個鍵并将字元串配置設定為值。 對于 North,CardinalDirection["North"] = 1 傳回值 1,CardinalDirection[1] = "North" 将值 "North" 配置設定給鍵 "1"。
這允許在數字成員的名稱和它們的值之間建立雙向關系。 要對此進行測試,請記錄以下内容:
console.log(CardinalDirection.North)
這将傳回“North”鍵的值:
Output 1
接下來,運作以下代碼來反轉引用的方向:
console.log(CardinalDirection[1])
這将輸出:
Output "North"
為了說明代表枚舉的最終對象,請将整個枚舉記錄到控制台:
console.log(CardinalDirection)
這将顯示建立雙向效果的兩組鍵/值對:
Output{
"1": "North",
"2": "East",
"3": "South",
"4": "West",
"North": 1,
"East": 2,
"South": 3,
"West": 4
}
了解了枚舉在 TypeScript 中是如何工作的,現在,我們将繼續使用枚舉在代碼中聲明類型。
在 TypeScript 中使用枚舉
在本節中,我們将嘗試在 TypeScript 代碼中将枚舉成員配置設定為類型的基本文法。 這可以通過與聲明基本類型相同的方式來完成。
要将 CardinalDirection 枚舉用作 TypeScript 中變量的類型,可以使用枚舉名稱,如以下突出顯示的代碼所示:
enum CardinalDirection {
North = 'N',
East = 'E',
South = 'S',
West = 'W',
};
const direction: CardinalDirection = CardinalDirection.North;
請注意,我們将變量設定為将枚舉作為其類型:
const direction: CardinalDirection= CardinalDirection.North;
我們還将變量值設定為枚舉的成員之一,在本例中為 CardinalDirection.North。 您可以這樣做,因為枚舉被編譯為 JavaScript 對象,是以它們除了作為類型之外還具有值表示。
如果傳遞的值與方向變量的枚舉類型不相容,如下所示:
const direction: CardinalDirection = false;
TypeScript 編譯器将顯示錯誤 2322:
OutputType 'false' is not assignable to type 'CardinalDirection'. (2322)
是以,方向隻能設定為 CardinalDirection 枚舉的成員。
我們還可以将變量的類型設定為特定的枚舉成員:
enum CardinalDirection {
North = 'N',
East = 'E',
South = 'S',
West = 'W',
};
const direction: CardinalDirection.North= CardinalDirection.North;
在這種情況下,該變量隻能配置設定給 CardinalDirection 枚舉的 North 成員。
如果我們的枚舉成員具有數值,我們還可以将變量的值設定為這些數值。 例如,給定枚舉:
enum CardinalDirection {
North = 1,
East,
South,
West,
};
我們可以将 CardinalDirection 類型的變量的值設定為 1:
const direction: CardinalDirection = 1;
這是可能的,因為 1 是 CardinalDirection 枚舉的 North 成員的值。 這僅适用于枚舉的數字成員,它依賴于編譯後的 JavaScript 對數字枚舉成員的雙向關系,在最後一節中介紹。
現在,我們已經嘗試使用枚舉值聲明變量類型,下一節将示範一種操作枚舉的特定方法:提取底層對象類型。
提取枚舉的對象類型
在前面的部分中,我們發現枚舉不僅是 JavaScript 之上的類型級擴充,而且具有實際值。 這也意味着枚舉資料結構本身具有類型,如果,我們嘗試設定表示枚舉執行個體的 JavaScript 對象,則必須考慮到該類型。 為此,我們需要提取枚舉對象表示本身的類型。
給定的 CardinalDirection 枚舉:
enum CardinalDirection {
North = 'N',
East = 'E',
South = 'S',
West = 'W',
};
嘗試建立一個與我們枚舉比對的對象,如下所示:
enum CardinalDirection {
North = 'N',
East = 'E',
South = 'S',
West = 'W',
};
const test1: CardinalDirection = {
North: CardinalDirection.North,
East: CardinalDirection.East,
South: CardinalDirection.South,
West: CardinalDirection.West,
}
在這段代碼中,test1 是一個 CardinalDirection 類型的對象,對象值包含了枚舉的所有成員。 但是,TypeScript 編譯器将顯示錯誤 2322:
OutputType '{ North: CardinalDirection; East: CardinalDirection; South: CardinalDirection; West: CardinalDirection; }' is not assignable to type 'CardinalDirection'.
出現此錯誤的原因是 CardinalDirection 類型表示所有枚舉成員的聯合類型,而不是枚舉對象本身的類型。
我們可以在枚舉名稱之前使用 typeof 來提取對象類型。 檢查下面突出顯示的代碼:
enum CardinalDirection {
North = 'N',
East = 'E',
South = 'S',
West = 'W',
};
const test1: typeof CardinalDirection = {
North: CardinalDirection.North,
East: CardinalDirection.East,
South: CardinalDirection.South,
West: CardinalDirection.West,
}
TypeScript 編譯器現在将能夠正确編譯代碼。
本節展示了一種擴充枚舉使用的特定方法。接下來,我們将研究一個适用于枚舉的用例:遊戲開發中的位标志。
使用帶有 TypeScript 枚舉的位标志
在本教程的最後一部分中,我們将了解 TypeScript 中枚舉的具體用例:位标志。
位标志是一種通過使用按位運算将不同的類似布爾值的選項表示為單個變量的方法。為此,每個标志必須使用 32 位數字中的一位,因為這是 JavaScript 在執行按位運算時允許的最大值。最大 32 位數為 2,147,483,647,二進制為 1111111111111111111111111111111,是以,我們有 31 個可能的标志。
假設,我們正在建構一個遊戲,玩家可能有不同的技能,例如 SKILL_A、SKILL_B 和 SKILL_C。為了確定我們的程式知道玩家何時具有某種技能,我們可以根據玩家的狀态制作可以打開或關閉的标志。
使用以下僞代碼,給每個技能标志一個二進制值:
SKILL_A = 0000000000000000000000000000001
SKILL_B = 0000000000000000000000000000010
SKILL_C = 0000000000000000000000000000100
現在,我們可以使用按位運算符 | 将玩家的所有目前技能存儲在一個變量中。 (要麼):
playerSkills = SKILL_A | SKILL_B
在這種情況下,為玩家配置設定位标志 0000000000000000000000000000001 和位标志 0000000000000000000000000000010 運算符将産生 0000000000000000000000000000011,這将代表玩家擁有兩種技能。
我們還可以添加更多技能:
playerSkills |= SKILL_C
這将産生 0000000000000000000000000000111 表示玩家擁有所有三個技能。
我們還可以使用按位運算符 & (AND) 和 ~ (NOT) 的組合删除技能:
playerSkills &= ~SKILL_C
然後要檢查玩家是否具有特定技能,請使用按位運算符 & (AND):
hasSkillC = (playerSkills & SKILL_C) == SKILL_C
如果玩家沒有 SKILL_C 技能,則 (playerSkills & SKILL_C) 部分将評估為 0。否則 (playerSkills & SKILL_C) 評估為我們正在測試的技能的确切值,在這種情況下為 SKILL_C (0000000000000000000000000000010 )。 通過這種方式,我們可以測試評估值是否與我們正在測試它的技能的值相同。
由于 TypeScript 允許我們将枚舉成員的值設定為整數,是以,我們可以将這些标志存儲為枚舉:
enum PlayerSkills {
SkillA = 0b0000000000000000000000000000001,
SkillB = 0b0000000000000000000000000000010,
SkillC = 0b0000000000000000000000000000100,
SkillD = 0b0000000000000000000000000001000,
};
我們可以使用字首 0b 直接表示二進制數。 如果我們不想使用如此大的二進制表示,可以使用按位運算符 <<(左移):
enum PlayerSkills {
SkillA = 1 << 0,
SkillB = 1 << 1,
SkillC = 1 << 2,
SkillD = 1 << 3,
};
1 << 0将評估到0b0000000000000000000000000000001、1 << 1到0b0000000000000000000000000000010、1 << 2到0b0000000000000000000000000000100和1 << 3到0b0000000000000000000000000001000。
現在,我們可以像這樣聲明 playerSkills 變量:
let playerSkills: PlayerSkills = PlayerSkills.SkillA | PlayerSkills.SkillB;
注意:必須将 playerSkills 變量的類型顯式設定為 PlayerSkills,否則 TypeScript 會推斷它為 number 類型。
要添加更多技能,我們将使用以下文法:
playerSkills |= PlayerSkills.SkillC;
我們還可以删除技能:
playerSkills &= ~PlayerSkills.SkillC;
最後,我們可以使用枚舉檢查玩家是否有任何給定的技能:
const hasSkillC = (playerSkills & PlayerSkills.SkillC) === PlayerSkills.SkillC;
雖然,仍在底層使用位标志,但該解決方案提供了一種更具可讀性群組織性的方式來顯示資料。 它還通過将二進制值作為常量存儲在枚舉中,并在 playerSkills 變量與位标志不比對時抛出錯誤,進而使我們的代碼更加類型安全。
總結
在大多數提供類型系統的語言中,枚舉是一種常見的資料結構,這在 TypeScript 中沒有什麼不同。
在本節教程中,我們在 TypeScript 中建立和使用了枚舉,同時還經曆了一些更進階的場景,例如,提取枚舉的對象類型和使用位标志。 使用枚舉,我們可以使代碼庫更具可讀性,同時,還将常量組織到資料結構中,而不是将它們留在全局空間中。
學習更多技能
請點選下方公衆号