如何在 TypeScript 中使用枚举

作者选择了 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_BSKILL_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 系列页面

Published At
Categories with 技术
comments powered by Disqus