枚举类型 -- Typescript基础篇(6)

988 阅读4分钟

枚举也是十分常见的类型。该类型的变量只能取限制范围内的值。如一年的月份只能取一月到十二月。

在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-initialized-error

理论上,数值枚举和字符串枚举可以混用,这类枚举称之为异构枚举,如:

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) {
  }
}