类型脚本有联合,那么枚举是多余的吗?

自从 TypeScript 引入了联合类型以来,我想知道是否有任何理由声明枚举类型。考虑以下枚举类型声明:

enum X { A, B, C }
var x: X = X.A;

以及类似的联合类型声明:

type X: "A" | "B" | "C"
var x: X = "A";

如果它们基本上服务于同样的目的,并且工会更加强大和富有表现力,那么为什么还需要枚举呢?

70136 次浏览

据我所知,它们并不是多余的,因为非常简单的原因,联合类型纯粹是一个编译时概念,而枚举实际上是透明的,并最终在结果 javascript (样本)中。

这允许您对枚举进行一些操作,而对于联合类型(比如 枚举可能的枚举值) ,这些操作在其他情况下是不可能的

您可能需要使用 enum的原因有几个

  • 可以在 enum上迭代。
  • 可以使用 enum作为 flags.比特旗
  • 下面是一些用例。 < a href = “ https://basarat.gitbook.io/TypeScript/type-system/Enums # Enums”rel = “ norefrer”> Enums TypeScript Deep 下潜

我认为使用联合的最大好处是它们提供了一种简洁的方式来表示具有多种类型的值,而且它们非常易读。 let x: number | string

编辑: 从 TypeScript 2.4开始,Enums 现在支持字符串。

enum Colors {
Red = "RED",
Green = "GREEN",
Blue = "BLUE",
}

枚举在概念上可以看作是联合类型的子集,专用于 int和/或 string值,还有其他响应中提到的一些附加特性,使其易于使用,例如 命名空间

关于类型安全性,数值枚举是较不安全的,然后是联合类型,最后是字符串枚举:

// Numeric enum
enum Colors { Red, Green, Blue }
const c: Colors = 100; // ⚠️ No errors!


// Equivalent union types
type Color =
| 0 | 'Red'
| 1 | 'Green'
| 2 | 'Blue';


let color: Color = 'Red'; // ✔️ No error because namespace free
color = 100; // ✔️ Error: Type '100' is not assignable to type 'Color'


type AltColor = 'Red' | 'Yellow' | 'Blue';


let altColor: AltColor = 'Red';
color = altColor; // ⚠️ No error because `altColor` type is here narrowed to `"Red"`


// String enum
enum NamedColors {
Red   = 'Red',
Green = 'Green',
Blue  = 'Blue',
}


let namedColor: NamedColors = 'Red'; // ✔️ Error: Type '"Red"' is not assignable to type 'Colors'.


enum AltNamedColors {
Red    = 'Red',
Yellow = 'Yellow',
Blue   = 'Blue',
}
namedColor = AltNamedColors.Red; // ✔️ Error: Type 'AltNamedColors.Red' is not assignable to type 'Colors'.

更多关于这个主题的2ality 文章: TypeScript 枚举: 它们是如何工作的? 它们可以用来做什么?


联合类型支持异构数据和结构,支持多态性,例如:

class RGB {
constructor(
readonly r: number,
readonly g: number,
readonly b: number) { }


toHSL() {
return new HSL(0, 0, 0); // Fake formula
}
}


class HSL {
constructor(
readonly h: number,
readonly s: number,
readonly l: number) { }


lighten() {
return new HSL(this.h, this.s, this.l + 10);
}
}


function lightenColor(c: RGB | HSL) {
return (c instanceof RGB ? c.toHSL() : c).lighten();
}

在枚举和联合类型之间,单例可以替换枚举。它更详细,但也更 面向对象:

class Color {
static readonly Red   = new Color(1, 'Red',   '#FF0000');
static readonly Green = new Color(2, 'Green', '#00FF00');
static readonly Blue  = new Color(3, 'Blue',  '#0000FF');


static readonly All: readonly Color[] = [
Color.Red,
Color.Green,
Color.Blue,
];


private constructor(
readonly id: number,
readonly label: string,
readonly hex: string) { }
}


const c = Color.Red;


const colorIds = Color.All.map(x => x.id);

我倾向于查看 F # 来查看良好的建模实践。关于 F # enums 的文章,关于 F # 的趣味和利润的一句话在这里很有用:

一般来说,您应该选择有区别的联合类型而不是枚举,除非您确实需要与它们相关联的 int (或 string)

模型枚举还有其他的替代方案,其中一些已经在另一篇2ality 文章 在 TypeScript 中替代枚举中进行了很好的描述。

枚举类型不是冗余的,但在大多数情况下,联合是首选的

但并非总是如此。使用枚举来表示例如状态转换可能比使用 union * * 更方便、更具表现力

考虑一下真实的场景:

enum OperationStatus {
NEW = 1,
PROCESSING = 2,
COMPLETED = 4
}


OperationStatus.PROCESSING > OperationStatus.NEW // true
OperationStatus.PROCESSING > OperationStatus.COMPLETED // false

使用 TypeScript 的最新版本,很容易声明可迭代的联合类型。因此,您应该选择联合类型而不是枚举。

如何声明可迭代联合类型

const permissions = ['read', 'write', 'execute'] as const;
type Permission = typeof permissions[number]; // 'read' | 'write' | 'execute'


// you can iterate over permissions
for (const permission of permissions) {
// do something
}

当联合类型的实际值不能很好地描述它们自己时,可以像使用枚举一样对它们进行命名。

// when you use enum
enum Permission {
Read = 'r',
Write = 'w',
Execute = 'x'
}


// union type equivalent
const Permission = {
Read: 'r',
Write: 'w',
Execute: 'x'
} as const;
type Permission = typeof Permission[keyof typeof Permission]; // 'r' | 'w' | 'x'


// of course it's quite easy to iterate over
for (const permission of Object.values(Permission)) {
// do something
}

不要错过在这些模式中起关键作用的 as const断言。

为什么使用枚举不好?

1. 非常量枚举不符合“ JavaScript 的类型化超集”的概念

我认为这个概念是 TypeScript 在其他 altJS 语言中如此流行的关键原因之一。 非常量枚举违反了这个概念,因为它发出的 JavaScript 对象生活在运行时,其语法与 JavaScript 不兼容。

2. Const 枚举有一些缺陷

Const 枚举不能与 Babel 一起翻译

目前有这个问题的 两个解决办法: 摆脱常量枚举手动或与插件 babel-plugin-const-enum

在环境上下文中声明常量枚举可能会有问题

当提供 --isolatedModules标志时,不允许使用环境常量枚举。 TypeScript 团队成员说,常量枚举在环境上下文中的 “ DT 上的 const enum真的没有意义”(DT 指的是 Definition itelyType)和 “应改为使用文字(字符串或数字)的联合类型”

--isolatedModules标志下的 Const 枚举即使在环境上下文之外也表现得很奇怪

在 GitHub 上读到 此评论时,我感到很惊讶,并确认 TypeScript 3.8.2中的行为仍然是正确的。

3. 数字枚举不是类型安全的

可以为数字枚举分配任意数字。

enum ZeroOrOne {
Zero = 0,
One = 1
}
const zeroOrOne: ZeroOrOne = 2; // no error!!

4. 字符串枚举的声明可能是多余的

我们有时会看到这种字符串枚举:

enum Day {
Sunday = 'Sunday',
Monday = 'Monday',
Tuesday = 'Tuesday',
Wednesday = 'Wednesday',
Thursday = 'Thursday',
Friday = 'Friday',
Saturday = 'Saturday'
}

我必须承认,有一个枚举特性是联合类型无法实现的

即使从上下文可以明显看出字符串值包含在枚举中,也不能将其赋给枚举。

enum StringEnum {
Foo = 'foo'
}
const foo1: StringEnum = StringEnum.Foo; // no error
const foo2: StringEnum = 'foo'; // error!!

这通过消除字符串值或字符串文字的使用,统一了整个代码中枚举值赋值的样式。这种行为与 TypeScript 类型系统在其他地方的行为不一致,这有点令人惊讶,一些认为这应该被修复的人提出了问题(这个这个) ,其中反复提到字符串枚举的意图是提供“不透明”的字符串类型: 也就是说,它们可以在不修改消费者的情况下进行更改。

enum Weekend {
Saturday = 'Saturday',
Sunday = 'Sunday'
}
// As this style is forced, you can change the value of
// Weekend.Saturday to 'Sat' without modifying consumers
const weekend: Weekend = Weekend.Saturday;

注意,这种“不透明性”并不完美,因为枚举值对字符串文字类型的赋值并不受限制。

enum Weekend {
Saturday = 'Saturday',
Sunday = 'Sunday'
}
// The change of the value of Weekend.Saturday to 'Sat'
// results in a compilation error
const saturday: 'Saturday' = Weekend.Saturday;

如果您认为这个“不透明”特性非常有价值,以至于可以接受我在上面描述的所有缺点来换取它,那么您就不能放弃字符串枚举。

如何从代码库中删除枚举

使用 ESLint 的 no-restricted-syntax规则,如 描述