目录

前言

一、枚举

1、数字枚举

2、字符串枚举

3、异构枚举

4、计算成员和常量成员

5、联合枚举与枚举成员的类型

6、运行时的枚举

7、编译时的枚举

8、反向映射

9、const枚举

二、外部枚举

三、对象与枚举


前言

枚举是TypeScript拥有的少数几个特性之一,它不是JavaScript的类型级扩展。使用枚举我们可以定义一些带名字的常量。使用枚举可以清晰地表达意图或创建一组有区别的用例。TypeScript支持数字的和基于字符串的枚举。

一、枚举

1、数字枚举

举一个例子说明啥叫数字枚举,和其他编程语言一个道理:

enum Direction{
    Up = 1,
    Down,
    Left,
    Right
}

我们定义了一个数字枚举,Up使用初始化为1,其余成员会从1开始自动增加,也就是Down为2,Left为3,Right为4。

我们也可以不完全使用初始化器:

enum Direction {
    Up,
    Down,
    Left,
    Right,
}

此时Up为0,接下来依次增长。注意每个枚举成员的值是不同的。

使用枚举也很简单,通过枚举的属性来访问枚举成员,枚举的名字来访问枚举类型:

enum Response{

    No = 0,
    Yes = 1,
}

function respond(recipient: string,message:Response): void {
    //...
}

respond("Princess Caroline",Response.Yes)

可以混合使用数字枚举,没有初始值设定项的枚举要么必须是第一个,要么必须在用数值常量或其他常量枚举成员初始化的数值枚举之后,不允许出现下面的情况:

enum E {
  A = getSomeValue(),
  B,
Enum member must have initializer.
}

2、字符串枚举

在字符串枚举中,每个成员都必须是用字符串文本或其他字符串枚举成员初始化的常量。

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

由于字符串枚举没有自增长的行为,字符串枚举可以很好的序列化。 换句话说,如果你正在调试并且必须要读一个数字枚举的运行时的值,这个值通常是很难读的 - 它并不能表达有用的信息(尽管 反向映射会有所帮助),字符串枚举允许你提供一个运行时有意义的并且可读的值,独立于枚举成员的名字。

3、异构枚举

枚举可以混合字符串和数字成员,但是没太大用:

enum BooleanLikeHeterogeneousEnum {
    No = 0,
    Yes = "YES",
}

除非你真的想要利用JavaScript运行时的行为,否则不建议这样做。

4、计算成员和常量成员

每个枚举成员都有一个与关联的值,可以是常数或者计算;

枚举成员在以下情况被视为常量:

(1)它是枚举中的第一个成员,它没有初始值设定项,在这种情况下,它被赋值0:

//E.X is constant
enum E {
    X,
}

(2)它没有初始值设定项,并且前面的枚举成员是数字在这种情况下,当前枚举成员的值将是前一个枚举成员的值加1。

//All enum members in 'E1' and 'E2' are constant.

enum E1{
    X,
    Y,
    Z,
}

enum E2{
    A=1,
    B,
    C,
}

(3)枚举成员是用常量枚举表达式初始化的;常量枚举表达式可以在编译时完全求值;它是TypeScript表达式的子集;表达式是常量枚举表达式,如果它是:

        ·文本枚举表达式(基本上是字符串文本或数字文本)

        ·对以前定义的常量枚举成员的引用(可以来自不同的枚举)

        ·带圆括号的常量枚举表达式

        ·其中之一 +,-,~应用于常量枚举表达式的一元运算符

        ·+,-,*,/,%,<<,>>,>>>,&,|,^以常量枚举表达式作为操作符的二元运算符

对于要计算到的常量枚举表达式,这是一个编译时错误NaN或无穷。

在所有其他情况下,enum成员被认为是计算的

enum FileAccess {
    // constant members
    None,
    Read =1 <<1,
    Write =1 <<2,
    ReadWrite = Read | Write,
    //computed member
    G ="123".length,
}

5、联合枚举与枚举成员的类型

存在一种特殊的非计算的常量枚举成员的子集:文本枚举成员。文本枚举成员是没有初始化值或值被初始化为常量的枚举成员

  • 任何字符串字面量(例如: "foo", "bar", "baz"
  • 任何数字字面量(例如: 1100
  • 应用了一元 -符号的数字字面量(例如: -1-100

当所有枚举成员都拥有文本枚举值时,一些特殊的语义就会发挥作用。

首先enum成员也会变成类型!例如:我们可以说某些成员可以只有具有枚举成员的值:

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可能会捕捉到错误比较值的错误。例如:

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

6、运行时的枚举

枚举是运行时存在的真实对象,可以传递给函数:

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);

7、编译时的枚举

即使枚举运行时存在真是的对象,但是keyof关键字的工作方式与你对典型对象的预期不同,相反,使用keyof获取将所有枚举表示为字符串类型。

enum LogLevel {
  ERROR,
  WARN,
  INFO,
  DEBUG,
}
 
/**
 * 这相当于:
 * 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:这是一条信息");

8、反向映射

除了为成员创建具有属性名称的对象外,数字枚举成员还可以获得反向映射从枚举值到枚举名称,例如:

enum Enum {
  A,
}
 
let a = Enum.A;
let nameOfA = Enum[a]; // "A"

TypeScript将其编译为以下JavaScript:

"use strict";
var Enum;
(function (Enum) {
    Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
let a = Enum.A;
let nameOfA = Enum[a]; // "A"

生成的代码中,枚举类型被编译成一个对象,它包含了正向映射( name -> value)和反向映射( value -> name)。 引用枚举成员总会生成为对属性访问并且永远也不会内联代码。

要注意的是不会为字符串枚举成员生成反向映射。

9、const枚举

在大多数情况下,枚举是一个完全有效的解决方案。但是有时候要求更严格,为了避免在访问枚举值是代码的开销,时间的消耗,我们可以 使用const枚举。常量枚举通过在枚举上使用const修饰符来定义。

const enum Enum{
    A=1,
    B=A*2
}

常量枚举只能使用常量枚举表达式,与常规枚举不同,它们在编译过程中被完全删除。常量枚举成员在使用的地方会被内联进来。之所以可以这么做是因为,常量枚举不允许包含计算机成员。

const enum Direction {
  Up,
  Down,
  Left,
  Right,
}
 
let directions = [
  Direction.Up,
  Direction.Down,
  Direction.Left,
  Direction.Right,
];

在生成的代码中

"use strict";
let directions = [
    0 /* Up */,
    1 /* Down */,
    2 /* Left */,
    3 /* Right */,
]

二、外部枚举

外部枚举用来描述已经存在的枚举类型的形状。

declare enum Enum {
    A = 1,
    B,
    C = 2
}

外部枚举和非外部枚举之间有一个重要区别,在正常的枚举里,没有初始化方法的成员被当成常数成员,对非常数的外部枚举而言,没有初始化方法时被当做需要经过计算的。

三、对象与枚举

在现在TypeScript中,当对象具有as const可以满足:

const enum EDirection{
    Up,
    Down,
    Left,
    Right,
}

const ODirection = {
    Up:0,
    Down:1,
    Left:2,
    Right:3,
} as const;

EDirection.Up;    
//(enum member) EDirection.Up = 0
ODirection.Up;       
//(property) Up: 0
// 使用枚举作为参数
function walk(dir: EDirection) {}
// 需要一个额外的引出钥匙
type Direction = typeof ODirection[keyof typeof ODirection];
function run(dir: Direction) {}
 
walk(EDirection.Left);
run(ODirection.Right);

支持这种格式而不是TypeScript格式的最大争论enum它使你的代码库与JavaScript的状态保持一致,并且什么时候枚举被添加到JavaScript中,然后您可以移动到其他语法。