深入理解tsenum

发布时间:2023-05-19

一、枚举类型的基本定义与使用

在 TypeScript 中,我们可以使用枚举类型(enum)来定义一组有标识意义的常量,它表示一种命名的整数常量集合。枚举类型使用关键字 enum 来定义:

enum Direction {
  Up,
  Down,
  Left,
  Right
}
console.log(Direction.Up); // 0

枚举类型默认从 0 开始递增,所以 Direction.Up 的值为 0Direction.Down 的值为 1,以此类推。我们也可以手动指定其中某些成员的值:

enum Direction {
  Up = 1,
  Down,
  Left = 2,
  Right
}
console.log(Direction.Up); // 1
console.log(Direction.Down); // 2
console.log(Direction.Left); // 2
console.log(Direction.Right); // 3

枚举类型中的成员是只读的,不能进行修改,而且在编译后会被转换成 JavaScript 代码,真正运行时并不存在枚举类型,只有常量值。

二、枚举类型的高级使用

1. 字符串枚举类型

枚举类型的成员不仅可以是数字,也可以是字符串,这种类型被称为字符串枚举类型。字符串枚举类型提供了更加直观的可读性:

enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT'
}
console.log(Direction.Up); // UP

注意:需要在将字符串指定为枚举成员的值时,需要使用类型断言来将字符串类型转换为枚举类型:

enum Direction {
  Up = <any>'UP',
  Down = <any>'DOWN',
  Left = <any>'LEFT',
  Right = <any>'RIGHT'
}

2. 常量枚举类型

常量枚举类型是在枚举类型前加上 const 关键字来定义的,其成员只在编译过程中起到作用,编译后将直接展开为常量变量:

const enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT'
}
console.log(Direction.Up); // UP

这样做的好处是,当我们只是需要使用枚举成员的值时,可以减少代码体积和运行时的开销。

3. 反向映射

枚举成员不仅可以通过成员名访问对应的值,还可以通过枚举值反向查找对应的成员名。具体实现如下:

enum Direction {
  Up = 'UP',
  Down = 'DOWN',
  Left = 'LEFT',
  Right = 'RIGHT'
}
console.log(Direction['UP']); // 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 = {}));

三、案例分析:使用tsenum实现状态机

状态机是一种常见的计算机程序设计方法,它将计算机系统作为一组状态和一组在这些状态之间的转换所组成的集合来表示。使用 tsenum 可以方便地实现状态机。 下面以一个简单的糖果机为例,来看看 tsenum 如何实现状态机:

enum State {
  NO_QUARTER, // 没有25分硬币
  HAS_QUARTER, // 有25分硬币
  SOLD, // 卖出糖果
  SOLD_OUT // 糖果售完
}
class GumballMachine {
  private state: State = State.NO_QUARTER;
  private count: number;
  constructor(count: number) {
    this.count = count;
    if (this.count > 0) {
      this.state = State.NO_QUARTER;
    } else {
      this.state = State.SOLD_OUT;
    }
  }
  public insertQuarter() {
    switch(this.state) {
      case State.NO_QUARTER:
        console.log('插入 25 分硬币');
        this.state = State.HAS_QUARTER;
        break;
      case State.HAS_QUARTER:
        console.log('已经有25分硬币了,请不要重复插入');
        break;
      case State.SOLD:
        console.log('请等待糖果掉落');
        break;
      case State.SOLD_OUT:
        console.log('糖果已经售完');
        break;
    }
  }
  public ejectQuarter() {
    switch(this.state) {
      case State.NO_QUARTER:
        console.log('您尚未插入25分硬币');
        break;
      case State.HAS_QUARTER:
        console.log('正在退回25分硬币');
        this.state = State.NO_QUARTER;
        break;
      case State.SOLD:
        console.log('已经出货,无法退回25分硬币');
        break;
      case State.SOLD_OUT:
        console.log('糖果已经售完,无法退回25分硬币');
        break;
    }
  }
  public turnCrank() {
    switch(this.state) {
      case State.NO_QUARTER:
        console.log('请先插入25分硬币');
        break;
      case State.HAS_QUARTER:
        console.log('正在出售糖果...');
        this.state = State.SOLD;
        this.dispense();
        break;
      case State.SOLD:
        console.log('正在出售糖果,请耐心等待');
        break;
      case State.SOLD_OUT:
        console.log('糖果已经售完,无法出售');
        break;
    }
  }
  public dispense() {
    switch(this.state) {
      case State.NO_QUARTER:
        console.log('请先插入25分硬币');
        break;
      case State.HAS_QUARTER:
        console.log('请先转动曲柄');
        break;
      case State.SOLD:
        console.log('正在出货,请等待');
        this.count--;
        if (this.count > 0) {
          this.state = State.NO_QUARTER;
        } else {
          console.log('糖果已经售完');
          this.state = State.SOLD_OUT;
        }
        break;
      case State.SOLD_OUT:
        console.log('糖果已经售完');
        break;
    }
  }
}
let machine = new GumballMachine(10);
machine.insertQuarter(); // 插入 25 分硬币
machine.turnCrank(); // 正在出售糖果...
machine.insertQuarter(); // 已经有25分硬币了,请不要重复插入
machine.ejectQuarter(); // 正在退回25分硬币
machine.turnCrank(); // 请先插入25分硬币

上述案例演示了如何使用 tsenum 实现一个状态机模型,可以通过状态来判断操作是否合法,并自动处理状态转换与状态动作,使得代码更加简介、易于维护。