枚举也是十分常见的类型。该类型的变量只能取限制范围内的值。如一年的月份只能取一月到十二月。
在ts中使用enum
关键字表示枚举类型:
enum Direction {
Up,
Down,
Left,
Right,
}
const d: Direction = Direction.Up;
与之前接口类型不同,默认情况下枚举类型会被最终编译到代码中,而不只是在编译前做类型检查,编译后的代码:
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 = {}));
可以看出,枚举被编译后是一个对象,具备由枚举成员,和枚举成员的值所组成的双向映射。所以当我们访问Direction.Up
时得到的值是0
,而访问Direction[0]
时得到的值是Up
。
数值枚举
如果我们没有手动为枚举赋值,那么该枚举是数字枚举,并且枚举成员被被赋值为从0
递增的数值。
上面例子等价于:
enum Direction {
Up = 0,
Down = 1,
Left = 2,
Right = 3,
}
console.log(Direction.Up === 0); // ture
console.log(Direction.Down === 1); // ture
console.log(Direction.Left === 2); // ture
console.log(Direction.Right === 3); // ture
我们也可以手动会枚举成员赋一个数值,并且在它之后成员对应的值会根据显式赋值递增:
enum Direction {
Up = 8,
Down = 10,
Left, // 11
Right, // 12
}
ts不会对枚举的值做覆盖检测。这就意味着,如果赋值冲突,ts并不会察觉并作出警告。此时会出现成员值被覆盖的情况。
enum Direction {
Up = 10,
Down = 9,
Left, // 此时的Left的值也是10
Right,
}
最终编译结果:
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 10] = "Up";
Direction[Direction["Down"] = 9] = "Down";
Direction[Direction["Left"] = 10] = "Left";
Direction[Direction["Right"] = 11] = "Right";
})(Direction || (Direction = {}));
字符串枚举
除了能为枚举成员赋值数值,也可以赋值字符串:
enum Direction {
Up = "up",
Down = "down",
Left = "left",
Right = "right",
}
字符串枚举的编译结果和数字枚举稍有不同,不再是双向映射,而是枚举成员名->值
的单向映射:
var Direction;
(function (Direction) {
Direction["Up"] = "up";
Direction["Down"] = "down";
Direction["Left"] = "left";
Direction["Right"] = "right";
})(Direction || (Direction = {}));
并且,字符串枚举不再具有自增行为,这就意味着,每一个成员都需要被赋值。

理论上,数值枚举和字符串枚举可以混用,这类枚举称之为异构枚举,如:
enum Direction {
Up,
Down = "down",
Left = 10,
Right = "right",
}
最终会被编译成:
"use strict";
var Direction;
(function (Direction) {
Direction[Direction["Up"] = 0] = "Up";
Direction["Down"] = "down";
Direction[Direction["Left"] = 10] = "Left";
Direction["Right"] = "right";
})(Direction || (Direction = {}));
但这不是一个好的实践,不建议这种使用方式。
常数项和计算所得项
不管是数值枚举还是字符串枚举,枚举成员的类型分为两种:常数项和计算所得项,上面所有例子都是常数项,而计算所得项如:
enum Color {
Red,
Yellow,
Blue = "blue".length,
}
最后一项Blue
的值是通过一个表达式计算得出,它就是计算所得项。
在官方定义中,只要满足以下任意一条就是常数项:
- 枚举是不具有表达式的数值枚举或字符串枚举,则每一项都是常数项(字符串枚举不允许包含计算所得项成员)
- 枚举成员使用常量表达式初始化,并且这个表达式在ts的编译阶段能够被求值
- 引用之前定义的常数枚举成员
+
,-
,~
一元运算符应用于常数枚举表达式+
,-
,*
,/
,%
,<<
,>>
,>>>
,&
,|
,^
二元运算符应用于常数枚举表达式
除以上的其他所有情况的枚举成员都被当做计算所得项。并且计算所得项的后面的每一项需要手动赋值(或者是紧跟其后的第一项手动赋值数值,后续自动推断)。否则会报错。
enum FileAccess {
// constant members
None,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
// computed member
G = "123".length
}
常数枚举
之前提到,默认情况下,enum
会被编译成一个对象,存在于最终的编译代码中,如果并不想生成额外的代码,可以使用常数枚举,常数枚举即在enum
关键字前加const
:
const enum Direction {
Up,
Down,
Left,
Right,
}
const a: Direction[] = [
Direction.Up,
Direction.Down,
Direction.Left,
Direction.Right,
];
编译后的结果:
const a = [
0 /* Up */,
1 /* Down */,
2 /* Left */,
3 /* Right */,
];
// 对比默认情况的编译结果
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 a = [
Direction.Up,
Direction.Down,
Direction.Left,
Direction.Right,
];
常数枚举都每一项都必须是常数项
如果希望即使是常数枚举,最终也保留编译的结果对象,可以开启preserveConstEnums
选项。
联合枚举和枚举成员类型
如果枚举的所有成员的值都是字面量时,枚举成员可以表示为一种类型,并且枚举本身变成了由所有成员组成的联合类型。
字面量值为数值,或字符串,或者应用了一元运算符
-
的数值(即负数)
enum ShapeKind {
Circle,
Square,
}
interface Circle {
kind: ShapeKind.Circle;
radius: number;
}
interface Square {
kind: ShapeKind.Square;
sideLength: number;
}
const c: Circle = {
// Error! Type 'ShapeKind.Square' is not assignable to type 'ShapeKind.Circle'.
kind: ShapeKind.Square,
radius: 100,
};
上面的ShapeKind
相当于ShapeKind.Square | ShapeKind.Circle
。此时ts能够利用枚举成员所组成的联合类型检测一些错误:
function foo(kind: ShapeKind) {
// This condition will always return 'true' since the types 'ShapeKind.Circle' and 'ShapeKind.Square' have no overlap.
if (kind !== ShapeKind.Circle || kind !== ShapeKind.Square) {
}
}