TS枚举

337 阅读4分钟

枚举是TypeScript的少数功能之一,它不是JavaScript的类型级扩展。

枚举允许开发者定义一组命名的常量。使用枚举可以使其更容易记录意图,或创建一组不同的情况。TypeScript提供了基于数字和字符串的枚举。

1 数值型枚举

我们首先从数字枚举开始,如果你来自其他语言,可能会更熟悉它。一个枚举可以用 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 UserResponse {
  No = 0,
  Yes = 1,
}
 
function respond(recipient: string, message: UserResponse): void {
  // ...
}
respond("Princess Caroline", UserResponse.Yes);

数字枚举可以混合在计算和常量成员中。简而言之,没有初始化器的枚举要么需要放在第一位,要么必须放在用数字常量或其他常量枚举成员初始化的数字枚举之后。换句话说,下面的情况是不允许的:

enum E {
  A = getSomeValue(),  
  B,  
  //  enum成员必须有初始化器。
}

2 字符串枚举

字符串枚举是一个类似的概念,但有一些细微的运行时差异,如下文所述。在一个字符串枚举中,每个成员都必须用一个字符串字头或另一个字符串枚举成员进行常量初始化。

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

虽然字符串枚举没有自动递增的行为,但字符串枚举有一个好处,那就是它们可以很好地 "序列化"。换句话说,如果你在调试时不得不读取一个数字枚举的运行时值,这个值往往是不透明的--它本身并不传达任何有用的意义(尽管 反向映射往往可以帮助你),字符串枚举允许你在代码运行时给出一个有意义的、可读的值,与枚举成员本身的名称无关。

3 联合枚举和枚举成员类型

  • 有一个特殊的常量枚举成员的子集没有被计算:字面枚举成员。字面枚举成员是一个没有初始化值的常量枚举成员,或者其值被初始化为
  • 任何字符串(例如:"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' 不能被分配给类型 'ShapeKind.Circle'
    radius: 100,
};

另一个变化是枚举类型本身有效地成为每个枚举成员的联盟。通过联合枚举,类型系统能够利用这一事实,即它知道存在于枚举本身的精确的值集。正因为如此,TypeScript可以捕捉到我们可能错误地比较数值的错误。比如说:

enum E {
  Foo,
  Bar,
}
 
function f(x: E) {
  if (x !== E.Foo || x !== E.Bar) {
    // 这个条件将总是返回'true',因为'E.Foo'和'E.Bar'的类型没有重合。
    //...
  }
}

在这个例子中,我们首先检查了x是否不是E.Foo。如果这个检查成功了,那么我们的 || 就会短路,'if'的主体就会运行。然而,如果检查没有成功,那么 x 就只能是 E.Foo,所以看它是否等于 E.Bar 就没有意义了。

4 对象与枚举

在现代TypeScript中,你可能不需要一个枚举,因为一个对象的常量就足够了:

const enum EDirection {
  Up,
  Down,
  Left,
  Right,
}
 
const ODirection = {
  Up: 0,
  Down: 1,
  Left: 2,
  Right: 3,
} as const;
 
// (enum member) EDirection.Up = 0
EDirection.Up;
           
// (property) Up: 0
ODirection.Up;
 
// 将枚举作为一个参数
function walk(dir: EDirection) {}
 
// 它需要一个额外的行来拉出数值
type Direction = typeof ODirection[keyof typeof ODirection];
function run(dir: Direction) {}
 
walk(EDirection.Left);
run(ODirection.Right);

与TypeScript的枚举相比,支持这种格式的最大理由是,它使你的代码库与JavaScript的状态保持一致,when/if枚举被添加到JavaScript中,那么你可以转移到额外的语法。