作者选择了 COVID-19 救援基金作为 Write for Donations计划的一部分接受捐款。
介绍
在 TypeScript, enums,或列出的类型,是持有一组恒定值的恒定长度数据结构。这些恒定值中的每一个都被称为 enum 的 member。 Enums在设置属性或只能是某些可能值的值时都是有用的。 一个常见的例子是玩卡板中单一卡片的服装值。 每张被抽取的卡片都会是一个俱乐部,一个钻石,一个心脏,或一个棒子; 这些四个之外没有可能的服装值,并且这些可能的值不太可能改变。 因此, enum 将是描述卡片可能服装的有效和清晰的方式。
虽然TypeScript的大多数功能都是在编译过程中丢失错误的有用,但 enums也作为数据结构有用,可以为您的代码保留常数。TypeScript在编译器发出的最终代码中将 enums转换为 JavaScript 对象。
本教程将解释用于创建 enum 类型的语法,TypeScript 编译器在帽子下创建的 JavaScript 代码,如何提取 enum 对象类型,以及涉及游戏开发中的 bit flags 的 enums 的用例。
前提条件
要遵循本教程,您将需要:
- 联合国 您可以执行 TypeScript 程序的环境与示例一起执行 。 要在您的本地机上设置此功能, 您需要安装如下:
- 既 [Node] (https://nodejs.org/en/about/) (又 [npm] (https://docs.npmjs.com/about-npm (或 [yarn] (https://yarnpkg.com/getting-started )) , 以便运行一个处理 TypeScript相关包的开发环境. 这个教程用Node.js版本14.3.0和npm版本6.14.5进行了测试. 要安装在 macOS 或 Ubuntu 18.04 上,请遵循 [如何在 macOS (https://andsky.com/tech/tutorials/how-to-install-node-js-and-create-a-local-development-environment-on-macos) 上安装节点并创建本地开发环境 或 ** 使用 [如何在 Ubuntu 18.04 (https://andsky.com/tech/tutorials/how-to-install-node-js-on-ubuntu-18-04 上安装节点.js 的PPA** 部分。 如果您正在使用 Linux (WSL) (https://andsky.com/tech/tutorials/how-to-install-the-windows-subsystem-for-linux-2-on-microsoft-windows-10) 的 Windows 子系统,此功能也会有效 。
- 此外,您需要安装在您的机器上的脚本编译器。 要做到这一点,请参考官方TypeScript网站.
- 如果您不想在本地机器上创建类型脚本环境, 您可以使用官方 [TypeScript Playground] (https://www.typescriptlang.org/play) 来跟踪 。
- 您需要足够的JavaScript知识,特别是ES6+语法,例如解构,休息参数,以及进出口. 如果您需要更多有关这些主题的信息,请读取我们的[JavaScript series How To Code (https://www.digitalocean.com/community/tutorial_series/how-to-code-in-javascript).
- 此教程会引用文本编辑器中支持TypeScript并显示行内错误的方面. 这对使用TypeScript是不必要的,但确实更多地利用了TypeScript的特性. 为了获得这些好处,您可以使用像[Visual Studio Code (https://code.visualstudio.com/)这样的文本编辑器,该编辑器对出框的TypeScript有完全的支持. 您可以在 [TypeScript Playground] (https://www.typescriptlang.org/play ) 中尝试这些好处. (英语)
本教程中显示的所有示例都是使用TypeScript版本 4.2.3创建的。
在 TypeScript 中创建 Enums
在本节中,您将通过声明一个 numeric enum 和一个 string enum 的示例运行。
TypeScript 中的字符串通常被用来表示给定值的选项数量。 这些数据被安排在一组关键/值对中。 虽然字符串必须是字符串,就像一般的 JavaScript 对象一样,字符串成员的值通常是自动增加的数字,主要用来区分一个成员与另一个成员。
若要创建一个数字enum,请使用enum
关键字,然后创建一个弯曲的支架(`{})块,在其中您将指定内部的enum成员,如下:
1enum CardinalDirection {
2 North = 1,
3 East,
4 South,
5 West,
6};
在本示例中,您正在创建一个名为CardinalDirection
的序列,其中有一个成员代表每个序列方向,而序列是一个适当的数据结构选择,以保持这些选项,因为值只有四个选项:北,南,东和西。
您将数字1
用作CardinalDirection
列表中的第一个成员的值,这将数字1
分配为North
的值,但您没有将值分配给其他成员,因为TypeScript会自动将剩余成员设置为前一个成员和一个成员的值。
此行为只适用于每个成员只有数字值的数值。
您也可以完全忽略设置 enum 成员的值:
1enum CardinalDirection {
2 North,
3 East,
4 South,
5 West,
6};
在这种情况下,TypeScript会将第一个成员设置为0
,然后将其他成员设置为自动基于该成员,逐一增加。
1enum CardinalDirection {
2 North = 0,
3 East = 1,
4 South = 2,
5 West = 3,
6};
TypeScript 编译器默认情况下会将数字分配给 enum 会员,但您可以忽略此项以创建一个字符串 enum. 这些字符串有每个会员的字符串值;当值需要具有某种可人读的含义时,这些字符串有用,例如如果您需要在以后读取日志或错误消息中的值。
您可以声明 enum 成员具有下列代码的字符串值:
1enum CardinalDirection {
2 North = 'N',
3 East = 'E',
4 South = 'S',
5 West = 'W'
6}
现在,每个方向都有一个字母值,表明它们与哪个方向绑定。
随着声明语法的覆盖,您现在可以检查底层的JavaScript,了解有关文本行为的更多信息,包括密钥 / 值对的双向性质。
双向 Enum 成员
在 TypeScript 编译中, enums 被翻译成 JavaScript 对象. 然而,enums 有几种特性可以将它们与对象区分开来。 它们提供比传统的 JavaScript 对象更稳定的数据结构来存储常规成员,并为 enum 成员提供双向引用。
使用您在上一节中创建的字符串 enum:
1enum CardinalDirection {
2 North = 'N',
3 East = 'E',
4 South = 'S',
5 West = 'W',
6};
在使用TypeScript编译器编译到JavaScript时,这将成为以下代码:
1"use strict";
2var CardinalDirection;
3(function (CardinalDirection) {
4 CardinalDirection["North"] = "N";
5 CardinalDirection["East"] = "E";
6 CardinalDirection["South"] = "S";
7 CardinalDirection["West"] = "W";
8})(CardinalDirection || (CardinalDirection = {}));
在此代码中,使用严格
字符串开始 严格模式,这是一种更具限制性的JavaScript版本。之后,TypeScript创建了一个没有值的变量CardinalDirection
。
在函数内部,一旦CardinalDirection
设置为空对象,则代码将多个属性分配给该对象:
1"use strict";
2var CardinalDirection;
3(function (CardinalDirection) {
4 CardinalDirection["North"] = "N";
5 CardinalDirection["East"] = "E";
6 CardinalDirection["South"] = "S";
7 CardinalDirection["West"] = "W";
8})(CardinalDirection || (CardinalDirection = {}));
请注意,每个属性是原始 enum 的一个成员,值设置为 enum 的成员值。
对于 string enums,这是过程的终结,但接下来你会尝试从最后一节的数字 enum 做同样的事情:
1enum CardinalDirection {
2 North = 1,
3 East,
4 South,
5 West,
6};
这将导致以下代码,加上突出的部分:
1"use strict";
2var CardinalDirection;
3(function (CardinalDirection) {
4 CardinalDirection[CardinalDirection["North"] = 1] = "North";
5 CardinalDirection[CardinalDirection["East"] = 2] = "East";
6 CardinalDirection[CardinalDirection["South"] = 3] = "South";
7 CardinalDirection[CardinalDirection["West"] = 4] = "West";
8})(CardinalDirection || (CardinalDirection = {}));
除了每个 enum 成员成为对象的属性(‘CardinalDirection[North
] = 1]’)外,enum 还为每个数字创建一个密钥,并将字符串分配为值。
这允许数字成员的名称和它们的值之间的双向关系. 要测试这一点,请登录以下内容:
1console.log(CardinalDirection.North)
这将返回北方
键的值:
1[secondary_label Output]
21
接下来,运行以下代码来扭转参考的方向:
1console.log(CardinalDirection[1])
产量将是:
1[secondary_label Output]
2"North"
要说明代表 enum 的最终对象,请将整个 enum 登录到控制台:
1console.log(CardinalDirection)
这将显示创建双向效应的钥匙/值对的两组:
1[secondary_label Output]
2{
3 "1": "North",
4 "2": "East",
5 "3": "South",
6 "4": "West",
7 "North": 1,
8 "East": 2,
9 "South": 3,
10 "West": 4
11}
有了了解Enums在TypeScript中如何在帽子下工作的了解,您现在将继续使用Enums来声明代码中的类型。
在 TypeScript 中使用 Enums
在本节中,您将尝试将 enum 成员分配为 TypeScript 代码中的类型的基本语法。
若要在 TypeScript 中将CardinalDirection
enum 作为变量类型,您可以使用 enum 名称,如下所示的突出代码所示:
1enum CardinalDirection {
2 North = 'N',
3 East = 'E',
4 South = 'S',
5 West = 'W',
6};
7
8const direction: CardinalDirection = CardinalDirection.North;
请注意,您正在设置变量以将 enum 作为其类型:
1const direction: CardinalDirection = CardinalDirection.North;
您还将变量值设置为 enum 的成员之一,在这种情况下 CardinalDirection.North
. 您可以这样做,因为 enums 被编译为 JavaScript 对象,因此它们除了类型之外,还具有值表示。
如果您传递了与方向
变量的 enum 类型不兼容的值,如下:
1const direction: CardinalDirection = false;
TypeScript 编译器将显示错误 2322
:
1[secondary_label Output]
2Type 'false' is not assignable to type 'CardinalDirection'. (2322)
因此,方向
只能设定给枢机指导
的成员。
您还可以将变量类型设置为特定的 enum 成员:
1enum CardinalDirection {
2 North = 'N',
3 East = 'E',
4 South = 'S',
5 West = 'W',
6};
7
8const direction: CardinalDirection.North = CardinalDirection.North;
在这种情况下,变量只能分配给北方
成员的CardinalDirection
单元。
如果您的 enum 成员有数值,您也可以将变量值设置为这些数值。
1enum CardinalDirection {
2 North = 1,
3 East,
4 South,
5 West,
6};
您可以将类型CardinalDirection
的变量值设置为1
:
1const direction: CardinalDirection = 1;
這是可能的,因為「1」是您的「CardinalDirection」序列中的「北」成員的值,這只適用於序列的數位成員,它依賴於編譯的JavaScript對數位序列成員的雙向關係,這是最後一節所涵蓋的。
现在你已经尝试了用 enum 值声明变量类型,下一节将展示一种特定的方法来操纵 enums:提取潜在的对象类型。
提取 Enums 对象类型
在之前的部分中,您发现 enums 不仅仅是 JavaScript 的类型级扩展,而且具有实际值,这也意味着 enum 数据结构本身有类型,如果您试图设置代表 enum 实例的 JavaScript 对象,则必须考虑到这种类型。
考虑到你的CardinalDirection
列表:
1enum CardinalDirection {
2 North = 'N',
3 East = 'E',
4 South = 'S',
5 West = 'W',
6};
尝试创建与您的 enum 相匹配的对象,如下:
1enum CardinalDirection {
2 North = 'N',
3 East = 'E',
4 South = 'S',
5 West = 'W',
6};
7
8const test1: CardinalDirection = {
9 North: CardinalDirection.North,
10 East: CardinalDirection.East,
11 South: CardinalDirection.South,
12 West: CardinalDirection.West,
13}
在此代码中,‘test1’是具有类型‘CardinalDirection’的对象,对象值包括 enum 的所有成员,但是,TypeScript 编译器将显示错误 `2322':
1[secondary_label Output]
2Type '{ North: CardinalDirection; East: CardinalDirection; South: CardinalDirection; West: CardinalDirection; }' is not assignable to type 'CardinalDirection'.
此错误的原因是,‘CardinalDirection’ 类型代表所有 enum 成员的联盟类型,而不是 enum 对象本身的类型。
1enum CardinalDirection {
2 North = 'N',
3 East = 'E',
4 South = 'S',
5 West = 'W',
6};
7
8const test1: typeof CardinalDirection = {
9 North: CardinalDirection.North,
10 East: CardinalDirection.East,
11 South: CardinalDirection.South,
12 West: CardinalDirection.West,
13}
TypeScript 编译器将能够正确编译您的代码。
接下来,你将通过一个应用案例,其中应用程序是适用的:游戏开发中的比特旗。
使用TypeScript Enums的Bit Flags
在本教程的最后一部分中,您将通过TypeScript中的实用使用案例运行: bit flags。
比特旗是用 bitwise 操作来代表不同的布尔式类型的选项的一种方式。 要做到这一点,每个旗帜必须使用一个 32 位数的精确位,因为这是 JavaScript 允许执行比特式操作的最大值。 最大 32 位数是 2,147,483,647',在二进制中是
11111111111111111111111111111,所以你有 `31'可能的旗帜。
假设你正在构建一个游戏,玩家可能具有不同的技能,如SKILL_A
,SKILL_B
和SKILL_C
。 要确保你的程序知道玩家什么时候拥有某些技能,你可以创建可以打开或关闭的旗帜,取决于玩家的状态。
使用以下假代码,给每个技能旗一个二进制值:
1SKILL_A = 0000000000000000000000000000001
2SKILL_B = 0000000000000000000000000000010
3SKILL_C = 0000000000000000000000000000100
您现在可以将玩家当前的所有技能存储在单个变量中,使用bitwise 运算符 ʔ
(OR):
1playerSkills = SKILL_A | SKILL_B
在这种情况下,将比特旗00000000000000000000000000000000001
和比特旗0000000000000000000000000000010
与``操作员分配给玩家,将产生00000000000000000000000000000000011
,这将代表玩家具有两种技能。
您还可以添加更多技能:
1playerSkills |= SKILL_C
这将产生0000000000000000000000000000111
,表示玩家拥有所有三种技能。
您还可以使用&
(AND) 和~
(NOT) 比特形运算符的组合删除技能:
1playerSkills &= ~SKILL_C
然后,要检查玩家是否具有特定技能,您可以使用 bitwise 运算符 &
(AND):
1hasSkillC = (playerSkills & SKILL_C) == SKILL_C
如果玩家没有SKILL_C
技能,则(PlayerSkills & Skill_C)
部分将评估为0
。否则(PlayerSkills & Skill_C)
将评估到你正在测试的技能的准确值,在这种情况下是SKILL_C
(000000000000000000000000010
)这样你就可以测试被评估的值与你正在测试的技能的值相同。
由于 TypeScript 允许您将 enum 成员的值设置为整数,您可以将这些旗帜存储为 enum:
1enum PlayerSkills {
2 SkillA = 0b0000000000000000000000000000001,
3 SkillB = 0b0000000000000000000000000000010,
4 SkillC = 0b0000000000000000000000000000100,
5 SkillD = 0b0000000000000000000000000001000,
6};
您可以使用前缀 0b
直接表示二进制数字. 如果您不希望使用这样的大二进制表示,您可以使用比特形运算符 <<
(left shift):
1enum PlayerSkills {
2 SkillA = 1 << 0,
3 SkillB = 1 << 1,
4 SkillC = 1 << 2,
5 SkillD = 1 << 3,
6};
「1 << 0」將評估為「0b00000000000000000000000000000000001」,「1 << 1」到「0b0000000000000000000000000000010」,「1 << 2」到「0b00000000000000000000000000000000100」,以及「1 << 3」到「0b00000000000000000000000000010001000」。
现在你可以这样声明你的PlayerSkills
变量:
1let playerSkills: PlayerSkills = PlayerSkills.SkillA | PlayerSkills.SkillB;
<$>[注]
**注:**您必须明确将PlayerSkills
变量类型设置为PlayerSkills
,否则TypeScript会推断它是数字
类型。
要添加更多的技能,你会使用以下语法:
1playerSkills |= PlayerSkills.SkillC;
您也可以删除一个技能:
1playerSkills &= ~PlayerSkills.SkillC;
最后,您可以检查玩家是否使用您的 enum 具有任何特定的技能:
1const hasSkillC = (playerSkills & PlayerSkills.SkillC) === PlayerSkills.SkillC;
虽然仍然在帽子下使用比特旗,但这种解决方案提供了更可读和有组织的方式来显示数据,它还通过将二进制值存储为常数,并在PlayerSkills
变量不匹配比特旗时丢失错误,从而使您的代码更安全。
结论
在大多数提供类型系统的语言中,enums是常见的数据结构,在TypeScript中没有什么不同. 在本教程中,您在TypeScript中创建和使用enums,同时也通过一些更先进的场景,例如提取enum的对象类型并使用比特旗。
有关TypeScript的更多教程,请参阅我们的 How To Code in TypeScript 系列页面。