玩转Typescript(七):TypeScript枚举

822 阅读4分钟

这是我参与11月更文挑战的第 7 天,活动详情查看:2021最后一次更文挑战

使用枚举我们可以定义一些带名字的常量。使用枚举可以清晰地表达意图或创建一组有区别的用例。TypeScript支持数字的和基于字符串的枚举。

数字枚举

比如,我们定义一个数字枚举, Up使用初始化为 1,其余的成员会从 1 开始自动增长。

enum Direction {
    Up = 1,
    Down,
    Left = 5,
    Right
}
let c: Direction = Direction.Up;
let d: Direction = Direction.Down;
let e: Direction = Direction.Left;
let f: Direction = Direction.Right;
console.log(c,d,e,f) //1 2 5 6

不初始化Up时,Up的值为 0,Down的值为1,依此类推。

字符串枚举

在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。

enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT"
}

字符串枚举没有自增长的行为。

使用枚举

我们可以通过枚举的属性来访问枚举成员,和枚举的名字来访问枚举类型:

enum MyEnum { No, Yes }
function respond(recipient: string, message: MyEnum): void {
  // ...
}
respond("Princess Caroline", MyEnum.Yes)

enum Color {Red, Green, Blue}
let c: Color = Color.Red;
let d: Color = Color.Green;
let e: Color = Color.Blue;
console.log(c,d,e) // 0,1,2

枚举成员

每个枚举成员都带有一个值,它可以是 常量计算出来的

枚举成员使用常量枚举表达式进行初始化。当一个表达式满足下面条件之一时,它就是一个常量枚举表达式:

  • 一个枚举表达式字面量(主要是字符串字面量或数字字面量)
  • 一个对之前定义的常量枚举成员的引用(可以是在不同的枚举类型中定义的)
  • 带括号的常量枚举表达式
  • 一元运算符 +, -, ~其中之一应用在了常量枚举表达式
  • 常量枚举表达式做为二元运算符 +, -, *, /, %, <<, >>, >>>, &, |, ^的操作对象。 若常数枚举表达式求值后为 NaNInfinity,则会在编译阶段报错。
enum FileAccess {
  // constant members
  None,
  Read = 1 << 1,
  Write = 1 << 2,
  ReadWrite = Read | Write,
  // computed member
  G = "123".length
}

当枚举成员的值包含字符串时,不允许在后续的枚举值中使用表达式进行计算:

enum Color {Red="hh", Green = Math.random(), Blue=4}
// error! Computed values are not permitted in an enum with string valued members.
let c: Color = Color.Red;
let d: Color = Color.Green;
let e: Color = Color.Blue;
console.log(c,d,e) // "hh",  0,  4

枚举成员的类型与联合枚举

字面量枚举成员是指不带有初始值的常量枚举成员,或者是值被初始化为

  • 任何字符串字面量(例如: "foo""bar""baz"
  • 任何数字字面量(例如: 1, 100
  • 应用了一元 -符号的数字字面量(例如: -1, -100

当所有枚举成员都拥有字面量枚举值时,它就带有了一种特殊的语义。

首先,枚举成员成为了类型

enum ShapeKind {
  Circle,
  Square
}
interface Circle {
  kind: ShapeKind.Circle;
  radius: number;
}
interface Square {
  kind: ShapeKind.Square;
  sideLength: number;
}
let c: Circle = {
  kind: ShapeKind.Square, // 这里,枚举成员ShapeKind.Square是类型
  // Error! Type 'ShapeKind.Square' is not assignable to type 'ShapeKind.Circle'.
  radius: 100,
}

其次,枚举类型本身变成了每个枚举成员的联合

enum E {
  Foo,
  Bar,
}
function f(x: E) {
  if (x !== E.Foo || x !== E.Bar) { // 永远为true。
    //             ~~~~~~~~~~~
    // Error! This condition will always return 'true' since the types 'E.Foo' and 'E.Bar' have no overlap.
  }
}

运行时的枚举

枚举是在运行时真正存在的对象。 例如下面的枚举:

enum E {
  X, Y, Z
}
function f(obj: { X: number }) {
  return obj.X;
}
f(E); // Works, since 'E' has a property named 'X' which is a number.

反向映射

除了创建一个以属性名做为对象成员的对象之外,数字枚举成员还具有了反向映射,从枚举值到枚举名字。

enum Enum {
  A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"

enum Color {Red, Green, Blue = 4}
let c: string = Color[0];
let d: string = Color[1];
let e: string = Color[2];
console.log(c,d,e) // "Red", "Green", undefined

const枚举

const 定义的枚举,在经过编译器编译后是一个对象,这个对象我们可以在程序运行时使用。

const enum E {
  Foo,
  Bar,
}
let num1: E = E.Foo

enum F {
  Foo,
  Bar,
}
let num2: F = F.Foo

编译结果:

let num1 = 0 /* Foo */;

var F;
(function (F) {
    F[F["Foo"] = 0] = "Foo";
    F[F["Bar"] = 1] = "Bar";
})(F || (F = {}));
let num2 = F.Foo;

可以看到编译后的代码中,const枚举直接用 0 进行替代,这样就节省了生成代码的开销。

declare枚举

外部枚举(declare枚举)用来描述已经存在的枚举类型。

declare enum Enum {
  A = 1,
  B,
  C = 2
}

外部枚举和非外部枚举之间有一个重要的区别,在正常的枚举里,没有初始化方法的成员被当成常数成员。 对于非常数的外部枚举而言,没有初始化方法时被当做需要经过计算的。

外部枚举类似于类型断言,只要有这个声明,意味着在当前开发环境上下文中一定存在当前这个对象,ts 类型系统能够侦测到当前整个文件目录上下文中的所有 declare 声明的变量,因此你可以随意使用当前对象。

参考