Skip to content
第 99 / 250 章前端⏱ 10 分钟阅读

第 99 章:枚举(Enum)

学习目标

  • 理解数字枚举、字符串枚举、异构枚举
  • 学会 const enum 的性能优化
  • 掌握枚举的替代方案(字面量联合)
  • 了解枚举的使用场景与陷阱

一、什么是枚举

枚举(Enum)是把一组具名常量组织起来的方式。

typescript
// 没有枚举时的写法
const StatusPending = 0;
const StatusPaid = 1;
const StatusShipped = 2;

// 用枚举
enum Status {
  Pending,    // 0
  Paid,       // 1
  Shipped     // 2
}

二、数字枚举

2.1 默认从 0 开始

typescript
enum Direction {
  Up,      // 0
  Down,    // 1
  Left,    // 2
  Right    // 3
}

console.log(Direction.Up);     // 0
console.log(Direction[0]);     // 'Up'  (反向映射)

2.2 自定义起始值

typescript
enum Status {
  Pending = 1,
  Paid,         // 2
  Shipped,      // 3
}

enum HttpCode {
  OK = 200,
  NotFound = 404,
  ServerError = 500
}

2.3 跳跃式赋值

typescript
enum Power {
  Low = 10,
  Medium,          // 11
  High = 100,
  Ultra            // 101
}

三、字符串枚举

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

// ❌ 数字枚举允许反向映射,字符串枚举不允许
console.log(Direction.Up);     // 'UP'
console.log(Direction['UP']);  // ❌ undefined

字符串枚举优点

  • 调试时输出可读
  • 序列化/反序列化更友好
  • 避免数字混淆
typescript
// ✅ 推荐:业务语义用字符串枚举
enum OrderStatus {
  Pending = 'PENDING',
  Paid = 'PAID',
  Shipped = 'SHIPPED',
  Delivered = 'DELIVERED',
  Cancelled = 'CANCELLED'
}

function updateOrder(status: OrderStatus) {
  console.log(`订单状态:${status}`);
}

updateOrder(OrderStatus.Paid);
// 输出:订单状态:PAID

四、异构枚举

混合数字和字符串,不推荐使用

typescript
enum Mixed {
  No = 0,
  Yes = 'YES'
}

企业级规范:禁止使用异构枚举,类型不一致会引发隐式转换 bug。

五、const enum(编译时优化)

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

const d = Direction.Up;

编译后

javascript
// 普通 enum:生成对象
const Direction = { Up: 0, Down: 1, Left: 2, Right: 3 };
const d = Direction.Up;

// const enum:直接内联值
const d = 0;  // 编译时替换

5.1 优点

  • ✅ 编译后没有对象,节省内存
  • ✅ 减少运行时查找

5.2 缺点

  • ❌ 不能跨模块用(isolatedModules 下报错)
  • ❌ 不能在 d.ts 中使用(会展开失败)

5.3 何时用

typescript
// ✅ 用 const enum:纯本地、高频调用
const enum Direction { Up, Down, Left, Right }

// ❌ 用普通 enum:需要 d.ts 暴露给外部
enum ApiStatus { OK, Error }

企业级规范:默认用普通 enum + 字面量联合;只在性能敏感处用 const enum。

六、枚举成员可以是函数吗?

枚举成员只能是基础类型(number、string、计算结果为常量的表达式):

typescript
enum Test {
  A = 1 + 2,            // ✅ 常量表达式
  B = Math.random(),     // ❌ Computed values are not permitted
  C = 'hello'.length     // ❌ Computed values are not permitted
}

// 编译时常量可以
enum FileAccess {
  Read = 1 << 1,         // 2
  Write = 1 << 2,        // 4
  ReadWrite = Read | Write  // 6
}

七、枚举作为类型

7.1 当作联合类型

typescript
enum Status {
  Active = 'ACTIVE',
  Inactive = 'INACTIVE'
}

// ✅ 接受枚举的特定成员
function setStatus(s: Status.Active | Status.Inactive) {
  // ...
}

setStatus(Status.Active);  // ✅
setStatus(Status.Inactive); // ✅

// ❌ 数字/字符串直接传会被拒绝
// setStatus('ACTIVE');  // ❌

7.2 反向访问的危险

typescript
enum Status {
  Pending = 0,
  Paid = 1
}

const status: number = 2;  // 数字 2 在 Status 中没有定义
// 但 TS 不会报错(因为 Status 是数字)

避免方式:用字符串枚举把"数字"和"枚举"区分开。

typescript
enum Status {
  Pending = 'PENDING',
  Paid = 'PAID'
}

const status: Status = 'ACTIVE';  // ❌ Type '"ACTIVE"' is not assignable to type 'Status'

八、为什么不推荐枚举?

8.1 现代 TS 推荐字面量联合

typescript
// ❌ 用 enum(运行时存在)
enum OrderStatus {
  Pending = 'PENDING',
  Paid = 'PAID'
}

// ✅ 用字面量联合(推荐)
type OrderStatus = 'PENDING' | 'PAID';

function update(status: OrderStatus) {
  // ...
}

update('PENDING');  // ✅

8.2 对比

特性enum字面量联合
运行时存在✅ 是❌ 否
编译后大小多了对象零开销
反向映射数字枚举有❌ 无
调试可读性✅ 字符串枚举好❌ 需要查类型
现代化推荐一般✅ TS 5.x 推荐

8.3 真实场景建议

typescript
// ① 业务状态(团队内部)→ 用字面量联合
type Status = 'pending' | 'paid' | 'shipped';

// ② 第三方 API 强约束、JSON 序列化 → 用 enum
enum HttpStatus {
  OK = 200,
  NotFound = 404
}

// ③ 性能敏感的常量(如协议字段)→ const enum
const enum Flag { Read = 1, Write = 2 }

九、枚举与 switch

typescript
enum Status {
  Active = 'ACTIVE',
  Inactive = 'INACTIVE',
  Pending = 'PENDING'
}

function handle(status: Status) {
  switch (status) {
    case Status.Active:
      return '活跃';
    case Status.Inactive:
      return '不活跃';
    case Status.Pending:
      return '待处理';
    // 漏掉一个 case,TS 不会报错(enum 不支持穷尽性检查)
  }
}

对比字面量联合

typescript
type Status = 'ACTIVE' | 'INACTIVE' | 'PENDING';

function handle(status: Status) {
  switch (status) {
    case 'ACTIVE':
      return '活跃';
    case 'INACTIVE':
      return '不活跃';
    case 'PENDING':
      return '待处理';
    default:
      const _exhaustive: never = status;  // ✅ 加新成员时这里会报错
      return _exhaustive;
  }
}

十、实战案例

10.1 业务状态管理

typescript
// 订单状态
enum OrderStatus {
  Draft = 'DRAFT',
  Submitted = 'SUBMITTED',
  Paid = 'PAID',
  Shipped = 'SHIPPED',
  Completed = 'COMPLETED',
  Cancelled = 'CANCELLED'
}

class Order {
  constructor(public id: number, public status: OrderStatus = OrderStatus.Draft) {}

  submit() {
    if (this.status !== OrderStatus.Draft) {
      throw new Error('只有草稿状态可以提交');
    }
    this.status = OrderStatus.Submitted;
  }

  pay() {
    if (this.status !== OrderStatus.Submitted) {
      throw new Error('只有已提交状态可以支付');
    }
    this.status = OrderStatus.Paid;
  }
}

10.2 权限位运算

typescript
enum Permission {
  Read = 1 << 0,        // 1
  Write = 1 << 1,       // 2
  Execute = 1 << 2,     // 4
  Admin = Read | Write | Execute  // 7
}

const userPerm: Permission = Permission.Read | Permission.Write;
// 校验权限
function hasPermission(p: Permission, required: Permission): boolean {
  return (p & required) === required;
}

hasPermission(userPerm, Permission.Read);     // true
hasPermission(userPerm, Permission.Execute); // false

十一、常见错误

11.1 枚举比较

typescript
enum A { X = 1 }
enum B { X = 2 }

const a: A = A.X;
const b: B = B.X;

console.log(a === b);  // ❌ 编译错误:This comparison appears unintentional

11.2 枚举成员相同值

typescript
enum A {
  X = 1,
  Y = 1  // ⚠️ 不会报错,但产生 reverse mapping 冲突
}

11.3 从非枚举类型赋值

typescript
enum Status { Active = 1 }
const num: number = 1;
const s: Status = num;  // ⚠️ 数字枚举允许,但实际可能不是合法值

十二、本章小结

要点关键
数字枚举默认从 0 开始,支持反向映射
字符串枚举推荐用于业务语义,无反向映射
const enum编译时内联,性能好但有使用限制
TS 5.x 推荐优先用字面量联合,避免 enum
何时用 enum第三方 API、JSON 序列化、强约束场景

动手练习

  1. 状态机:用数字枚举实现一个 TrafficLight(红/黄/绿),写一个切换函数
  2. 字面量替代:把练习 1 改成字面量联合,比较两种写法的优劣
  3. 权限系统:用位运算 enum 实现一套权限管理(Read/Write/Execute/Delete)

推荐阅读


下一章第 100 章:类型断言与类型守卫

本站基于 VitePress 构建 · 由 Codebook 团队维护