枚举类型

枚举可以清晰地表达意图或创建一组有区别的用例。TypeScript支持数字的和基于字符串的枚举

数字枚举

1
2
3
4
5
6
enum Direction {
Up = 1,
Down,
Left,
Right
}

Up下方的成员从1开始依次增加。如果未声明Up为1,则默认为0。

使用枚举

通过枚举的属性来访问枚举成员,和枚举的名字来访问枚举类型

1
2
3
4
5
6
7
8
9
10
enum Response {
No = 0,
Yes = 1,
}

function respond(recipient: string, message: Response): void {
// ...
}

respond("Princess Caroline", Response.Yes)

数字枚举可以被混入到计算过的和常量成员。 简短地说,没有初始化器的成员要么在首位,要么必须在用数值常量或其他常量枚举成员初始化的数值枚举之后

1
2
3
4
enum E {
A = getSomeValue(),
B, // Error! Enum member must have initializer.
}

字符串枚举

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

由于字符串枚举没有自增长的行为,字符串枚举可以很好的序列化,即可读性更强。

可以通过反向映射加强数字枚举的可读性

异构枚举

从技术的角度来说,枚举可以混合字符串和数字成员

除非你真的想要利用JavaScript运行时的行为,否则不建议这样做

计算的结果和常量成员

枚举成员的值可以是常量或计算出来的。

当满足如下条件时,枚举成员被当作是常量:

  • 是枚举的第一个成员且没有初始化器,此时被赋值为0
  • 不带有初始化器且它之前的枚举成员是一个 数字 常量
  • 枚举成员使用 常量枚举表达式 初始化
    • 当一个表达式满足下面条件之一时,它就是一个常量枚举表达式
      • 一个枚举表达式字面量
      • 一个对之前定义的常量枚举成员的引用
      • 带括号的常量枚举表达式
      • 一元运算符+, -, ~其中之一应用在了常量枚举表达式
      • 常量枚举表达式做为二元运算符+, -, *, /, %, <<, >>, >>>, &, |, ^的操作对象

若常量枚举表达式求值后为NaN或Infinity,则会在编译阶段报错

所有其它情况的枚举成员被当作是需要计算得出的值

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

存在一种特殊的非计算的常量枚举成员的子集:字面量枚举成员

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

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

当所有枚举成员都拥有字面量枚举值时,枚举成员成为了类型

我们可以说某些成员 只能 是枚举成员的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enum ShapeKind {
Circle,
Square,
}

interface Circle {
kind: ShapeKind.Circle;
radius: number;
}

interface Square {
kind: ShapeKind.Square;
sideLength: number;
}

let c: Circle = {
kind: ShapeKind.Square, // Error! Type 'ShapeKind.Square' is not assignable to type 'ShapeKind.Circle'.
radius: 100,
}

另一个变化是枚举类型本身变成了每个枚举成员的 联合
通过联合枚举,类型系统能够利用这样一个事实,它可以知道枚举里的值的集合,从而捕获在比较值时犯得错误

1
2
3
4
5
6
7
8
9
10
11
enum E {
Foo,
Bar,
}

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

运行时的枚举

枚举是在运行时真正存在的对象

1
2
3
4
5
6
7
8
9
10
11
enum E {
X, Y, Z
}

function f(obj: { X: number }) {
return obj.X;
}

// 没问题,因为 'E'包含一个数值型属性'X'。
f(E);

编译时的枚举

尽管一个枚举是在运行时真正存在的对象,但keyof关键字的行为与其作用在对象上时有所不同

应该使用keyof typeof来获取一个表示枚举里所有字符串key的类型

1
2
3
4
5
6
7
8
9
enum LogLevel {
ERROR, WARN, INFO, DEBUG
}

/**
* 等同于:
* type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
*/
type LogLevelStrings = keyof typeof LogLevel;

对于数字枚举在编译时会自动添加反向映射

为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用const枚举

常量枚举只能使用常量枚举表达式,并且不同于常规的枚举,它们在编译阶段会被删除。 常量枚举成员在使用的地方会被内联进来。 之所以可以这么做是因为,常量枚举不允许包含计算成员

作者

徐云飞

发布于

2022-10-29

更新于

2023-02-05

许可协议

评论