TypeScript 的 Enum 类型
Enum 是 TypeScript 新增的一种数据结构和类型,中文译为“枚举”。
简介
实际开发中,经常需要定义一组相关的常量。
const RED = 1;
const GREEN = 2;
const BLUE = 3;
let color = userInput();
if (color === RED) {/* */}
if (color === GREEN) {/* */}
if (color === BLUE) {/* */}
throw new Error('wrong color');
上面示例中,常量RED、GREEN、BLUE是相关的,意为变量color的三个可能的取值。它们具体等于什么值其实并不重要,只要不相等就可以了。
TypeScript 就设计了 Enum 结构,用来将相关常量放在一个容器里面,方便使用。
enum Color {
  Red,     // 0
  Green,   // 1
  Blue     // 2
}
上面示例声明了一个 Enum 结构Color,里面包含三个成员Red、Green和Blue。第一个成员的值默认为整数0,第二个为1,第三个为2,以此类推。
使用时,调用 Enum 的某个成员,与调用对象属性的写法一样,可 以使用点运算符,也可以使用方括号运算符。
let c = Color.Green; // 1
// 等同于
let c = Color['Green']; // 1
Enum 结构本身也是一种类型。比如,上例的变量c等于1,它的类型可以是 Color,也可以是number。
let c:Color = Color.Green; // 正确
let c:number = Color.Green; // 正确
上面示例中,变量c的类型写成Color或number都可以。但是,Color类型的语义更好。
Enum 结构的特别之处在于,它既是一种类型,也是一个值。绝大多数 TypeScript 语法都是类型语法,编译后会全部去除,但是 Enum 结构是一个值,编译后会变成 JavaScript 对象,留在代码中。
// 编译前
enum Color {
  Red,     // 0
  Green,   // 1
  Blue     // 2
}
// 编译后
let Color = {
  Red: 0,
  Green: 1,
  Blue: 2
};
上面示例是 Enum 结构编译前后的对比。
由于 TypeScript 的定位是 JavaScript 语言的类型增强,所以官方建议谨慎使用 Enum 结构,因为它不仅仅是类型,还会为编译后的代码加入一个对象。
Enum 结构比较适合的场景是,成员的值不重要,名字更重要,从而增加代码的可读性和可维护性。
enum Operator {
  ADD,
  DIV,
  MUL,
  SUB
}
function compute(
  op:Operator,
  a:number,
  b:number
) {
  switch (op) {
    case Operator.ADD:
      return a + b;
    case Operator.DIV:
      return a / b;
    case Operator.MUL:
      return a * b;
    case Operator.SUB:
      return a - b;
    default:
      throw new Error('wrong operator');
  }
}
compute(Operator.ADD, 1, 3) // 4
上面示例中,Enum 结构Operator的四个成员表示四则运算“加减乘除”。代码根本不需要用到这四个成员的值,只用成员名就够了。
TypeScript 5.0 之前,Enum 有一个 Bug,就是 Enum 类型的变量可以赋值为任何数值。
enum Bool {
  No,
  Yes
}
function foo(noYes:Bool) {
  // ...
}
foo(33);  // TypeScript 5.0 之前不报错
上面示例中,函数foo的参数noYes是 Enum 类型,只有两个可用的值。但是,TypeScript 5.0 之前,任何数值作为函数foo的参数,编译都不会报错,TypeScript 5.0 纠正了这个问题。
另外,由于 Enum 结构编译后是一个对象,所以不能有与它同名的变量(包括对象、函数、类等)。
enum Color {
  Red,
  Green,
  Blue
}
const Color = 'red'; // 报错
上面示例,Enum 结构与变量同名,导致报错。
很大程度上,Enum 结构可以被对象的as const断言替代。
enum Foo {
  A,
  B,
  C,
}
const Bar = {
  A: 0,
  B: 1,
  C: 2,
} as const;
if (x === Foo.A) {}
// 等同于
if (x === Bar.A) {}
上面示例中,对象Bar使用了as const断言,作用就是使得它的属性无法修改。这样的话,Foo和Bar的行为就很类似了,前者完全可以用后者替代,而且后者还是 JavaScript 的原生数据结构。
Enum 成员的值
Enum 成员默认不必赋值,系统会从零开始逐一递增,按照顺序为每个成员赋值,比如0、1、2……