我想强调如何保持你的TypeScript代码整洁的方法。这个系列的文章具有很强的观点性,所以如果我抛弃了一个你学会喜欢的功能,请不要生气。这不是针对你的。
今天我们来看一下枚举。枚举是我看到来自Java或C#等语言的人经常使用的一个特性,因为它们在那里非常突出。枚举也是TypeScript的 "旧时代 "的一个特征,当时的JavaScript环境与现在有很大的不同。你可以看到,枚举的工作方式与TypeScript中的其他类型特别不同。
枚举发出的代码#
我最喜欢写TypeScript的方式是
- 编写常规的、现代的JavaScript。
- 在任何可以加强TypeScript对我们代码理解的地方添加类型。
这意味着在编译步骤之后,你最终会得到与之前相同的代码,而没有额外的类型定义。
Enums,像类一样,同时创建一个类型和一个值。这意味着,例如,这个声明。
enum Direction {
Up,
Down,
Left,
Right,
}
会在JavaScript输出中发出代码。
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Down"] = 1] = "Down";
Direction[Direction["Left"] = 2] = "Left";
Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));
如果你使用const enum ,你可以摆脱输出,但我经常看到人们到处使用普通的枚举,并想知道为什么他们的输出变得如此之大。特别是如果你把前端和后端之间的 "胶水代码 "放在枚举中,你就会出现巨大的文件和包。
好吧,这是一件事,我们可以通过强制执行const enums来处理这个问题。但也有这种讨厌的模糊性。
数值枚举不是类型安全的#
是的,你没听错。普通的数字枚举--就像你不设置字符串值的枚举一样--不是类型安全的!如果我们回头看一下前面的Direction 枚举,一个函数如果取一个方向,也会取任何数字值来代替。
enum Direction {
Up,
Down,
Left,
Right,
}
declare function move(direction: Direction): void;
move(30);
// ☝️ This is totally ok! 😱
原因是有一个用数字枚举实现比特掩码的用例。而人们似乎真的在这么做!快速搜索一下 "TypeScript枚举比特掩码 "或 "比特标志",可以看到很多实现和例子。枚举为这种情况提供了语法上的糖。我想说的是,为什么这种情况可以在JavaScript中实现,我怀疑它是你最常使用枚举的情况。
通常情况下,你想确保你只能传递真正有效的值。
到目前为止,数字枚举是如此。但是还有字符串枚举,对吗?它们是类型安全的,不是吗?是的。而且它们是很特别的
字符串枚举是命名的类型#
在一个结构化类型的世界里,枚举选择成为一个命名类型。这意味着即使值是有效的和兼容的,你也不能把它们传递给一个你期望得到字符串枚举的函数或对象。请看这个例子。
enum Status {
Admin = "Admin",
User = "User",
Moderator = "Moderator",
}
declare function closeThread(threadId: number, status: Status): void;
closeThread(10, "Admin");
// ^ 💥 This is not allowed!
closeThread(10, Status.Admin);
// ^ You have to be explicit!
这是你可以利用的东西,但它也与数字枚举和TypeScript的整个其他类型系统的工作方式有很大不同。
倾向于联合类型#
一个简单的联合类型给了你类似的工作方式,并且与TypeScript更加一致。
type Status = "Admin" | "User" | "Moderator"
declare function closeThread(threadId: number, status: Status): void;
closeThread(10, "Admin");
// All good 😄
你可以从枚举中获得所有的好处,比如适当的工具和类型安全,而不需要再多走一步,冒着输出你不想要的代码的风险。你需要传递什么,以及从哪里获取值,也变得更加清晰。不需要仅仅为了手动映射后端字符串到一个枚举。简单、清晰、整洁!
如果你想把你的代码写成枚举式的,有一个对象和一个命名的标识符,一个const 对象和一个Values 辅助类型可能就会给你带来想要的行为,而且更接近于JavaScript*(注意,这不是我喜欢或推荐的方式,简单的联合类型通常就足够了*)。
const Direction = {
Up: 0,
Down: 1,
Left: 2,
Right: 3,
} as const;
// Get to the const values of any object
type Values<T> = T[keyof T];
// Values<typeof Direction> yields 0 | 1 | 2 | 3
declare function move(
direction: Values<typeof Direction>): void;
move(30);
// ^ 💥 This breaks!
move(0);
// ^ 👍 This works!
move(Direction.Left);
// ^ 👍 This also works!
// And now for the Status enum
const Status = {
Admin: "Admin",
User: "User",
Moderator: "Moderator"
} as const;
// Values<typeof Status> yields "Admin" | "User" | "Moderator"
declare function closeThread(
threadId: number,
status: Values<typeof Status>): void;
closeThread(10, "Admin"); // All good!
closeThread(10, Status.User); // enum style
也没有什么意外。
- 你知道在输出中你最终会得到什么代码。
- 你不会因为有人决定从一个字符串枚举到一个数字枚举而最终改变行为。
- 你在你需要的地方拥有类型安全。
- 而且你给你的同事和用户提供了与枚举相同的便利。
但公平地说,一个简单的字符串联合类型就能满足你的需要。类型安全、自动完成、可预测的行为。
当然,你可以学习并记住枚举的所有特殊性,并知道如何处理它们。但是,如果在类型系统中完全有一种更清晰、更容易的方法来实现同样的--如果不是更好的--类型安全,那又何必呢?这就是为什么我建议选择联合类型而不是枚举。