TypeScript深受开发者社区的喜爱,原因有很多,其中之一是它为用其编写的代码提供了静态检查。
在开发生命周期的早期发现问题,可以节省数天的调试随机的、模糊的错误,这些错误在使用JavaScript等动态语言时有时会突然出现。
TypeScript可以帮助使你的代码更具可预测性和更好的文档化,使重构更容易,并帮助减少你在生产应用中运行时可能面临的潜在错误。它的受欢迎程度和力量体现在其93%的开发者满意率和过去五年的使用量激增上。
对TypeScript至关重要的一种语言机制是枚举
什么是TypeScript中的枚举?
枚举并不是类型化的一个特征,有趣的是,就像 TypeScript 的大多数特征一样 - 事实上,它们是少数几个、增强语言的新特征之一。
枚举允许开发者为一个变量定义一套严格的选项。比如说:
enum Door {
Open,
Closed,
Ajar // half open, half closed
}
Enums默认为数字枚举,所以上面的枚举本质上是一个以0,1,2 为键的对象,我们可以在转写的JavaScript代码中看到:
"use strict";
var Door;
(function (Door) {
Door[Door["Open"] = 0] = "Open";
Door[Door["Closed"] = 1] = "Closed";
Door[Door["Ajar"] = 2] = "Ajar"; // half open, half closed
})(Door || (Door = {}));
console.log(Door.FullyOpened);
在TypeScript中,你也可以使用字符串枚举,像这样:
enum Door {
Open = "open",
Closed = "closed",
Ajar = "ajar" // half open, half closed
}
如果你再使用这个Door 枚举,你可以确保变量只使用枚举中指定的三个选项。所以,你不可能意外地分配错误的东西,也不可能通过这种方式轻易地产生bug。
如果你真的试图使用另一个变量,它就会抛出这样一个类型错误。
enum Door {
Open = "open",
Closed = "closed",
Ajar = "ajar" // half open, half closed
}
console.log(Door.FulyOpened)
Property 'FullyOpened' does not exist on type 'typeof Door'.
为什么我们需要扩展一个枚举?
扩展是面向对象的四大支柱之一,是TypeScript中存在的一种语言特性。扩展一个枚举允许你本质上复制一个变量定义,并向其添加一些额外的东西。
所以,举例来说,你可能想做这样的事情:
enum Door {
Open = "open",
Closed = "closed",
Ajar = "ajar" // half open, half closed
}
enum DoorFrame extends Door { // This will not work!
Missing = "noDoor"
}
console.log(DoorFrame.Missing)
我们可以在一个枚举中添加额外的属性,甚至可以将两个枚举合并在一起,以便在我们的枚举上仍然获得严格的类型,同时也可以在它们被定义之后对其进行修改。
但是请注意,上面的代码片段不起作用了!它不能转译,并抛出了一个问题。它不能转译,并且抛出了四个不同的错误。
你能扩展枚举吗?
简短的回答是不能,你不能扩展枚举,因为TypeScript没有提供语言功能来扩展它们。然而,你可以利用一些变通方法来实现继承的目的。
TypeScript中的类型交叉
enum Door {
Open = "open",
Closed = "closed",
Ajar = "ajar" // half open, half closed
}
enum DoorFrame {
Missing = "noDoor"
}
type DoorState = Door | DoorFrame;
在上面的代码块中,我们使用了一个交叉类型。交叉的作用就像一个 "或",这简单地意味着DoorState 类型要么是Door ,要么是DoorFrame 。
现在这意味着DoorState 可以互换地使用两个枚举中的任何一个变量。
扩展语法
我们在前面的转译代码中看到,一个枚举会变成一个JavaScript对象,具有你的枚举所指定的键和值。
在TypeScript中,如果我们愿意,我们可以写出纯粹的JavaScript。事实上,这是TypeScript的一个很大的优势。例如,你可以把你所有的file.js 改名为file.ts ,并关闭编译器对你的代码的检查。只要你运行编译/转译步骤,一切都会正常工作,而不需要改变代码。
我们可以利用这一点,知道当我们的枚举变成JavaScript时,它将是一个JavaScript对象字面,并使用传播语法,像下面这样:
enum Move {
LEFT = 'Left',
RIGHT = 'Right',
FORWARD = 'Forward',
BACKWARD = 'Backward'
}
const myMove = {
...Move,
JUMP: 'Jump'
}
不过,这个解决方案已经被描述得很次要了,因为它并不像交集类型那样是一个好的解决方案,因为它不像我们的第一个解决方案那样稳健。这是因为你的枚举的 "组成 "是在运行时发生的,而当我们使用类型交叉时,类型检查可以在编译/转写时发生,而不是运行时。
TypeScript 枚举的最佳实践
我们已经讨论了如何在Typescript中扩展枚举,但是枚举并不是用来解决所有问题的灵丹妙药。枚举如果使用不当,会使你的代码可读性、可扩展性和可维护性变差,而不是改善你的代码。
因此,让我们介绍一下在TypeScript中使用枚举时的一些最佳实践和常见模式。
1.避免异质枚举
我已经解释了我们如何能够拥有像这样的字符串枚举:
enum Seasons {
Summer = "Summer",
Winter = "Winter",
Spring = "Spring",
Fall = "Fall"
}
同时也有像这样的数字枚举:
enum Decision {
Yes,
No
}
但是,有第三种类型的枚举,你可能不知道,叫做异质枚举。这就是你可以在同一个枚举中使用字符串和数字枚举。
文档中的一个例子是这样的:
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES",
}
值得注意的是,即使是文档也不鼓励这种做法,因为在这个例子中,使用这种方法表明你很可能需要:
- 重新考虑这两个变量之间的关系
- 创建两个独立的枚举
- 使它们都符合一个数据类型
2."枚举作为配置 "的反模式
有时,代码功能会被迫遵守一个枚举选项,这很快就会变成一种反模式。
这里有一个例子
enum Operators {
Add,
Subtract
}
function calculate(op: Operators, firstNumber: number, secondNumber: number) {
switch(op) {
case Operators.Add: return firstNumber + secondNumber
case Operators.Subtract: return firstNumber - secondNumber
}
}
上面的代码看起来相当简单和安全,因为我们的例子确实是简单和安全的。
但是在大型代码库中,当你像这样严格地将实现细节与枚举类型捆绑在一起时,你会引起一些问题:
- 你创造了两个真理的来源(如果枚举发生变化,枚举和函数都需要被更新)
- 这种模式会在代码中散布元数据
- 该代码块不再是通用的了
如果你需要做类似上述的事情,一个更简单(也更浓缩)的模式可以是这样的:
const Operators = {
Add: {
id: 0,
apply(firstNumber: number, secondNumber: number) { return firstNumber + secondNumber }
},
Subtract: {
id: 1,
apply(firstNumber: number, secondNumber: number) { return firstNumber - secondNumber }
}
}
3.枚举最能代表的数据类型
通常有一种方法可以将代码中利用的不同类型的数据分组:离散变量或连续变量。
离散变量是指在其表示方法之间有空格的数据,并且只有几种表示方法。这里有几个例子:
- 一周的日子
- 星期一
- 星期二
- 周三
- 星期四
- 星期五
- 礼拜六
- 阳光
- 季节
- 夏季
- 冬季
- 春季
- 秋天
离散数据是一个很好的候选者,可以放在枚举里面,它可以帮助代码的清晰度和重复使用。连续数据指的是没有空隙的数据,它们落入一个连续的序列,比如数字。这些数据可以根据它们的测量结果而有所不同:
- 某人的体重
- 一辆汽车的速度
离散数据是一个很好的候选数据,它可以在枚举中使用,而连续数据不应该在枚举中使用。你能想象一个用于年龄的枚举吗?
enum Age {
Zero,
One,
Two,
Three,
Four,
Five,
Six
}
这不是一个放在枚举中的好候选数据,因为它需要不断地更新和修正,导致维护的噩梦。
你应该只在枚举中添加离散的、高度稳定的数据类型。
总结
我希望这篇文章是有用的,这样你就能更好地理解什么是枚举,它们所解决的问题,合并两个枚举的用例,以及你可能实现的方法祝你黑客活动愉快。