枚举
枚举
枚举允许我们定义一组命名常量,使用枚举可以将代码含义文档化或创建不重复的类型值。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! Enum member must have initializer.
}
字符串枚举
字符串枚举是类似的概念,但在运行时有一些细微的差别,字符串枚举的每一个成员必须使用字符串常量或另外的成员来初始化。
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
字符串枚举没有自动递增的概念,字符串枚举的好处是可以很好地“序列化”。也就是说,当你在调试代码或查看运行时数字枚举的值时,这些值通常都是不透明的,它们本身并没有传达任何有用的信息(反查通常可以提供一些帮助)。字符串枚举允许在代码运行时使用有意义和可读性高的值,值与枚举成员本身的名称无关。
异构枚举
从技术上来说枚举中是可以同时存在数字枚举成员和字符串枚举成员的,但这种用法不好。
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES",
}
除非你真的想巧妙地利用 JavaScript 的运行时能力,否则建议你不要这样使用枚举。
计算成员和常量成员
每个枚举成员都有一个值,这个值可以是常量也可以是计算后的值,下列情况,枚举成员的值是常量。
- 它是枚举的第一个成员并且没有被初始化,这种情况下默认值是 0。
// E.X is constant:
enum E { X }
- 枚举成员没有被初始化且枚举成员前面有一个数字常量成员。在这种情况下,枚举成员的值将是前一个枚举成员的值加 1。
// All enum members in 'E1' and 'E2' are constant.
enum E1 { X, Y, Z }
enum E2 {
A = 1, B, C
}
- 枚举成员使用常量表达式初始化,常量表达式是 TypeScript 表达式的子集,可以在编译期对其进行完成求值。表达式包含下面的类型。
- 字面量(可以是字符串也可以是数字)
- 对之前其他常量枚举成员的引用(可以来自不同的枚举)
- 带括号的常量枚举表达式
- 对常量枚举表达式使用 +、-、~ 等一元表达式而形成的新的表达式
- 以常量枚举表达式作为操作数的二元运算符,运算符包括 +、-、*、/、%、<<、>>、>>>、&、|、^
常量枚举表达式计算值为 NaN 或 Infinity 将出现编译期错误。
在所有其他情况下,枚举成员被视为计算值。
enum FileAccess {
// constant members
None,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
// computed member
G = "123".length
}
联合枚举和枚举成员类型
字面量枚举成员是不需要计算的,字面量枚举成员或者没有初始化值,或者使用下面的值初始化。
- 任何字符串字面量(例如
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, // Error! Type 'ShapeKind.Square' is not assignable to type 'ShapeKind.Circle'.
radius: 100,
}
另一个变化是枚举可以视为一个联合类型,虽然目前还没有讨论联合类型,你只需要知道,使用枚举,类型系统可以知道枚举本身存在的成员集合。因此,TypeScript 可以捕获错误的赋值引发的 BUG。例如。
~实际上说的就是把枚举当做一个联合类型来用
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.
}
}
在例子中,我们首先检查 x 不是 E.Foo,如果验证通过,那么 || 及后面的内容将被忽略,然后 if 代码块中的内容将被执行。如果验证失败,那么x 只能是 E.Foo,因此,检查 x 是否等于 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);
编译时枚举
枚举是在运行时存在的真实对象,keyof 关键字有可能期望的结果不一致,使用 keyof typeof 获取 Type,该 Type 将所有枚举成员表示为字符串。
enum LogLevel {
ERROR, WARN, INFO, DEBUG
}
/**
* This is equivalent to:
* type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
*/
type LogLevelStrings = keyof typeof LogLevel;
function printImportant(key: LogLevelStrings, message: string) {
const num = LogLevel[key];
if (num <= LogLevel.WARN) {
console.log('Log level key is: ', key);
console.log('Log level value is: ', num);
console.log('Log level message is: ', message);
}
}
printImportant('ERROR', 'This is a message');
枚举反查
除了创建具有意义的枚举成员名称外,数字枚举成员还可以从枚举值映射到枚举的名称。例如。
enum Enum {
A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"
TypeScript 将把上面的代码编译成如下代码。
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
var a = Enum.A;
var nameOfA = Enum[a]; // "A"
在生成的代码中,枚举被编译成对象,对象存储了从键到值以及从值到键的映射关系。其它的枚举成员都会被定义为该对象的属性。
但是要记住,字符串枚举不会生成这样的映射关系。
常量枚举
大多数情况下,枚举都是一个完美有效的解决方案。但有时候会有更加严格的要求,为避免生成额外的代码和降低调用时的开销,可以使用常量枚举,常量枚举需要在普通枚举前面加上关键字 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 */];
Ambient enums
这种枚举用于定义已经存在的枚举类型。
declare enum Enum {
A = 1,
B,
C = 2
}
这种类型的枚举与普通枚举最大的不同在于,在普通枚举中,如果没有初始化的枚举成员前面的成员是常量成员,那么这个没有被初始化的成员将被认为是常量成员。与此不同的是,这种类型的枚举(非常量枚举)成员如果没有被初始化,那么成员始终被当做计算类成员处理。