Enums
枚举
枚举
枚举允许我们定义一组命名常量。使用枚举可以更容易地记录意图,或者创建一组不同的案例。TypeScript提供了数字和基于字符串的枚举。
数字枚举
我们首先从数字枚举开始,如果你来自其他语言,这可能更为熟悉。可以使用enum
关键字定义枚举。
enum Direction {
Up = 1,
Down,
Left,
Right,
}
上面,我们有一个数字枚举,其中Up
初始化为1
。以下所有成员都从这一点开始自动递增。换句话说,Direction.Up
价值1
,Down
拥有2
,Left
拥有3
和Right
拥有4
。
如果我们想要,我们可以完全忽略初始化器:
enum Direction {
Up,
Down,
Left,
Right,
}
这里Up
有价值0
,Down
会有1
,等等。这种自动递增行为对于我们可能不关心成员值本身的情况是有用的,但是确实在意每个值与同一个枚举中的其他值不同。
使用枚举很简单:只需从枚举本身访问任何成员作为属性,并使用枚举的名称声明类型:
enum Response {
No = 0,
Yes = 1,
}
function respond(recipient: string, message: Response): void {
// ...
}
respond("Princess Caroline", Response.Yes)
数字枚举可以在计算和常量成员中混合(见下文)。简而言之,没有初始化器的枚举要么必须是第一个,要么必须在用数字常量或其他常量枚举成员初始化的数字枚举之后。换句话说,以下是不允许的:
enum E {
A = getSomeValue(),
B, // error! 'A' is not constant-initialized, so 'B' needs an initializer
}
字符串枚举
字符串枚举是一个类似的概念,但具有一些细微的运行时间差异,如下文所述。在一个字符串枚举中,每个成员必须用字符串文字或其他字符串枚举成员进行常量初始化。
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
虽然字符串枚举不具有自动递增行为,但字符串枚举具有“序列化”好处。换句话说,如果您正在调试并且必须读取数值枚举的运行时值,那么该值通常是不透明的 - 它本身没有传达任何有用的含义(尽管反向映射通常可以帮助),字符串枚举允许您在代码运行时提供有意义且可读的值,而与枚举成员本身的名称无关。
异构枚举
技术上的枚举可以与字符串和数字成员混合使用,但不清楚为什么你会这样做:
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES",
}
除非你真的想以聪明的方式利用JavaScript的运行时行为,否则建议你不要这样做。
计算和不断的成员
每个枚举构件都具有与其相关联的值,其可以是恒定的
或计算的
。在以下情况下,enum成员被认为是常量:
- 它是枚举中的第一个成员,它没有初始化器,在这种情况下,它被分配了值
0
:// EX是常量:枚举E {X}
- a literal enum expression (basically a string literal or a numeric literal)
- a reference to previously defined constant enum member (which can originate from a different enum).
- a parenthesized constant enum expression
- one of the `+`, `-`, `~` unary operators applied to constant enum expression
- `+`, `-`, `*`, `/`, `%`, `<<`, `>>`, `>>>`, `&`, `|`, `^` binary operators with constant enum expressions as operands It is a compile time error for constant enum expressions to be evaluated to `NaN` or `Infinity`.
在所有其他情况下,枚举成员被认为是计算的。
enum FileAccess {
// constant members
None,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
// computed member
G = "123".length
}
联合枚举和枚举成员类型
有一个不计算常量枚举成员的特殊子集:字面枚举成员。文字枚举成员是一个常量枚举成员没有初始化值,或与初始化值
- 任何字符串文字(例如
"foo"
,"bar
,"baz"
)
当枚举中的所有成员都有字面枚举值时,就会发挥一些特殊的语义。
首先,枚举成员也是类型的!例如,我们可以说某些成员只能
拥有一个枚举成员的价值:
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!
radius: number,
}
另一个变化是枚举类型本身有效地成为每个枚举成员的联合
。尽管我们尚未讨论union类型,但您需要知道的一点是,使用union枚举类型系统能够利用这样一个事实,即它知道enum本身存在的确切值集合。正因为如此,TypeScript可以捕捉到我们可能错误比较值的愚蠢错误。例如:
enum E {
Foo,
Bar,
}
function f(x: E) {
if (x !== E.Foo || x !== E.Bar) {
// ~~~~~~~~~~~
// Error! Operator '!==' cannot be applied to types 'E.Foo' and 'E.Bar'.
}
}
在该示例中,我们首先检查是否x
是没有
E.Foo
。如果检查成功,那么我们||
将会短路,'if'的主体将会运行。但是,如果检查没有
成功,则x
可以只
为E.Foo
,所以它是没有
意义的,看它是否等于E.Bar
。
运行时枚举
枚举是运行时存在的真实对象。例如,下面的枚举
enum E {
X, Y, Z
}
实际上可以传递给函数
function f(obj: { X: number }) {
return obj.X;
}
// Works, since 'E' has a property named 'X' which is a number.
f(E
反向映射
除了为成员创建具有属性名称的对象之外,数字枚举成员还可以获得从枚举值到枚举名称的反向映射
。例如,在这个例子中:
enum Enum {
A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"
TypeScript可能会将其编译为如下所示的JavaScript:
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {})
var a = Enum.A;
var nameOfA = Enum[a]; // "A"
在这个生成的代码中,一个枚举被编译成一个存储forward(name- > value)和reverse(value- > name)映射的对象。对其他枚举成员的引用总是作为属性访问发出并且从不内联。
请记住,字符串枚举成员根本不会
生成反向映射。
const 枚举
在大多数情况下,枚举是完全有效的解决方案。不过有时候需求会更紧张。为了避免在访问枚举值时支付额外的生成代码和额外间接成本,可以使用const
枚举。常量枚举const
在我们的枚举上使用修饰符来定义:
const enum Enum {
A = 1,
B = A * 2
}
常量枚举只能使用常量枚举表达式,而不像常规枚举,它们在编译期间会被完全删除。常量枚举成员在使用地点内联。这是可能的,因为常量枚举不能计算成员。
const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]
在生成的代码将成为
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
环境枚举
环境枚举用于描述已经存在的枚举类型的形状。
declare enum Enum {
A = 1,
B,
C = 2
}
环境和非环境枚举之间的一个重要区别是,在常规枚举中,如果前一个枚举成员被认为是常量,那么没有初始化方的成员将被视为常量。相反,没有初始化器的环境(和非const)枚举成员始终
被认为是计算的。