Skip to content

数字枚举

概述

数字枚举(Numeric Enums)是 TypeScript 中默认的枚举类型,其成员值是数字。数字枚举具有自动递增、反向映射等特性,是管理有序常量集合的理想选择。理解数字枚举的特性对于处理状态码、优先级、顺序等场景非常重要。

数字枚举在编译后会生成 JavaScript 对象,既可以在编译时进行类型检查,也可以在运行时访问枚举值。数字枚举特别适合需要数值比较、范围判断或需要反向映射的场景。

基本特性

自动递增

数字枚举的成员如果没有显式指定值,会从 0 开始自动递增:

typescript
enum Status {
  Pending,    // 0
  Processing, // 1
  Completed,  // 2
  Cancelled   // 3
}

console.log(Status.Pending);    // 0
console.log(Status.Processing); // 1
console.log(Status.Completed);  // 2
console.log(Status.Cancelled);  // 3

自定义初始值

可以手动指定第一个成员的值,后续成员会在此基础上自动递增:

typescript
enum Status {
  Pending = 10,    // 10
  Processing,      // 11(自动递增)
  Completed,       // 12(自动递增)
  Cancelled        // 13(自动递增)
}

console.log(Status.Pending);    // 10
console.log(Status.Processing); // 11
console.log(Status.Completed);  // 12
console.log(Status.Cancelled);  // 13

部分指定值

可以在枚举中间指定某些成员的值,后续成员会从最后一个指定值开始递增:

typescript
enum Status {
  Pending = 10,     // 10
  Processing,       // 11(自动递增)
  Completed = 100,  // 100(手动指定)
  Cancelled,        // 101(自动递增)
  Failed            // 102(自动递增)
}

console.log(Status.Pending);    // 10
console.log(Status.Processing); // 11
console.log(Status.Completed);  // 100
console.log(Status.Cancelled);  // 101
console.log(Status.Failed);     // 102

提示

部分指定值时要小心,确保递增逻辑符合预期。建议要么全部自动递增,要么全部手动指定,避免混淆。

反向映射

数字枚举的一个重要特性是反向映射(Reverse Mapping),可以通过数值获取对应的枚举成员名称:

typescript
enum Status {
  Pending,
  Processing,
  Completed
}

// 正向访问:通过名称获取值
console.log(Status.Pending);    // 0
console.log(Status.Processing); // 1
console.log(Status.Completed);  // 2

// 反向映射:通过值获取名称
console.log(Status[0]);  // "Pending"
console.log(Status[1]);  // "Processing"
console.log(Status[2]);  // "Completed"

反向映射的实现原理

数字枚举在编译后会生成一个双向映射对象:

typescript
// TypeScript 代码
enum Status {
  Pending,
  Processing,
  Completed
}

// 编译后的 JavaScript 代码(简化版)
var Status;
(function (Status) {
    Status[Status["Pending"] = 0] = "Pending";
    Status[Status["Processing"] = 1] = "Processing";
    Status[Status["Completed"] = 2] = "Completed";
})(Status || (Status = {}));

这创建了一个双向映射:

  • Status.Pending0
  • Status[0]"Pending"

使用反向映射的场景

反向映射在以下场景中非常有用:

typescript
enum HttpStatus {
  OK = 200,
  Created = 201,
  BadRequest = 400,
  NotFound = 404,
  InternalServerError = 500
}

// 从 HTTP 响应码获取状态名称
function getStatusName(code: number): string {
  return HttpStatus[code] || "Unknown";
}

console.log(getStatusName(200));  // "OK"
console.log(getStatusName(404));  // "NotFound"
console.log(getStatusName(999));  // "Unknown"

// 日志记录:同时记录数值和名称
function logResponse(status: HttpStatus): void {
  console.log(`Response: ${status} (${HttpStatus[status]})`);
}

logResponse(HttpStatus.OK);  // "Response: 200 (OK)"
logResponse(HttpStatus.NotFound);  // "Response: 404 (NotFound)"

注意

反向映射只适用于数字枚举。字符串枚举不支持反向映射,因为字符串枚举的成员值是字符串,不能作为对象的键。

使用示例

示例 1:HTTP 状态码

使用数字枚举定义 HTTP 状态码,利用数值进行范围判断:

typescript
enum HttpStatus {
  // 2xx 成功
  OK = 200,
  Created = 201,
  Accepted = 202,
  NoContent = 204,
  
  // 3xx 重定向
  MovedPermanently = 301,
  Found = 302,
  NotModified = 304,
  
  // 4xx 客户端错误
  BadRequest = 400,
  Unauthorized = 401,
  Forbidden = 403,
  NotFound = 404,
  MethodNotAllowed = 405,
  
  // 5xx 服务器错误
  InternalServerError = 500,
  BadGateway = 502,
  ServiceUnavailable = 503
}

// 使用数值范围判断响应类型
function handleResponse(status: HttpStatus): void {
  if (status >= 200 && status < 300) {
    console.log(`成功: ${HttpStatus[status]} (${status})`);
  } else if (status >= 400 && status < 500) {
    console.log(`客户端错误: ${HttpStatus[status]} (${status})`);
  } else if (status >= 500) {
    console.log(`服务器错误: ${HttpStatus[status]} (${status})`);
  }
}

handleResponse(HttpStatus.OK);                    // "成功: OK (200)"
handleResponse(HttpStatus.NotFound);              // "客户端错误: NotFound (404)"
handleResponse(HttpStatus.InternalServerError);   // "服务器错误: InternalServerError (500)"

// 使用反向映射获取状态名称
function getStatusMessage(status: HttpStatus): string {
  const statusName = HttpStatus[status];
  switch (status) {
    case HttpStatus.OK:
      return `${statusName}: 请求成功`;
    case HttpStatus.Created:
      return `${statusName}: 资源已创建`;
    case HttpStatus.NotFound:
      return `${statusName}: 资源未找到`;
    default:
      return `${statusName}: 未知状态`;
  }
}

console.log(getStatusMessage(HttpStatus.OK));        // "OK: 请求成功"
console.log(getStatusMessage(HttpStatus.NotFound));  // "NotFound: 资源未找到"

示例 2:任务优先级

使用数字枚举定义任务优先级,数值越大优先级越高:

typescript
enum Priority {
  Low = 1,      // 低优先级
  Medium = 2,   // 中优先级
  High = 3,     // 高优先级
  Critical = 4  // 紧急
}

interface Task {
  id: number;
  title: string;
  priority: Priority;
  completed: boolean;
}

class TaskManager {
  private tasks: Task[] = [];

  addTask(task: Task): void {
    this.tasks.push(task);
    // 按优先级排序:数值越大越靠前
    this.tasks.sort((a, b) => b.priority - a.priority);
  }

  getTasksByPriority(minPriority: Priority): Task[] {
    return this.tasks.filter(task => task.priority >= minPriority);
  }

  getPriorityName(priority: Priority): string {
    return Priority[priority];
  }

  printTasks(): void {
    this.tasks.forEach(task => {
      const priorityName = this.getPriorityName(task.priority);
      console.log(`[${priorityName}] ${task.title}`);
    });
  }
}

const manager = new TaskManager();

manager.addTask({ id: 1, title: "写文档", priority: Priority.Low, completed: false });
manager.addTask({ id: 2, title: "修复 bug", priority: Priority.Critical, completed: false });
manager.addTask({ id: 3, title: "代码审查", priority: Priority.Medium, completed: false });

manager.printTasks();
// [Critical] 修复 bug
// [Medium] 代码审查
// [Low] 写文档

const highPriorityTasks = manager.getTasksByPriority(Priority.High);
console.log(highPriorityTasks.length);  // 1(只有 Critical)

示例 3:游戏状态机

使用数字枚举实现游戏状态机,利用自动递增简化状态定义:

typescript
enum GameState {
  Menu,        // 0: 主菜单
  Loading,     // 1: 加载中
  Playing,     // 2: 游戏中
  Paused,      // 3: 暂停
  GameOver,    // 4: 游戏结束
  Victory      // 5: 胜利
}

class Game {
  private state: GameState = GameState.Menu;

  transitionTo(newState: GameState): void {
    const oldStateName = GameState[this.state];
    const newStateName = GameState[newState];
    
    console.log(`状态转换: ${oldStateName} → ${newStateName}`);
    
    this.state = newState;
    this.handleStateChange();
  }

  private handleStateChange(): void {
    switch (this.state) {
      case GameState.Menu:
        console.log("显示主菜单");
        break;
      case GameState.Loading:
        console.log("加载游戏资源...");
        // 模拟加载完成后自动进入游戏
        setTimeout(() => {
          this.transitionTo(GameState.Playing);
        }, 1000);
        break;
      case GameState.Playing:
        console.log("游戏进行中...");
        break;
      case GameState.Paused:
        console.log("游戏已暂停");
        break;
      case GameState.GameOver:
        console.log("游戏结束");
        break;
      case GameState.Victory:
        console.log("恭喜胜利!");
        break;
    }
  }

  getState(): GameState {
    return this.state;
  }

  getStateName(): string {
    return GameState[this.state];
  }

  // 检查是否可以转换到目标状态
  canTransitionTo(targetState: GameState): boolean {
    const current = this.state;
    
    // 定义允许的状态转换规则
    const allowedTransitions: Record<GameState, GameState[]> = {
      [GameState.Menu]: [GameState.Loading],
      [GameState.Loading]: [GameState.Playing, GameState.Menu],
      [GameState.Playing]: [GameState.Paused, GameState.GameOver, GameState.Victory],
      [GameState.Paused]: [GameState.Playing, GameState.Menu],
      [GameState.GameOver]: [GameState.Menu],
      [GameState.Victory]: [GameState.Menu]
    };

    return allowedTransitions[current]?.includes(targetState) ?? false;
  }
}

const game = new Game();

game.transitionTo(GameState.Loading);
// 状态转换: Menu → Loading
// 显示加载中...

// 1秒后自动转换到 Playing
// 状态转换: Loading → Playing
// 游戏进行中...

game.transitionTo(GameState.Paused);
// 状态转换: Playing → Paused
// 游戏已暂停

console.log(`当前状态: ${game.getStateName()}`);  // "当前状态: Paused"
console.log(game.canTransitionTo(GameState.Playing));  // true
console.log(game.canTransitionTo(GameState.Victory));  // false

示例 4:错误代码系统

使用数字枚举定义错误代码,利用数值范围进行分类:

typescript
enum ErrorCode {
  // 1xx: 系统错误
  SystemError = 100,
  DatabaseError = 101,
  NetworkError = 102,
  
  // 2xx: 验证错误
  ValidationError = 200,
  InvalidInput = 201,
  MissingRequired = 202,
  
  // 3xx: 权限错误
  Unauthorized = 300,
  Forbidden = 301,
  TokenExpired = 302,
  
  // 4xx: 业务错误
  NotFound = 400,
  AlreadyExists = 401,
  InsufficientFunds = 402
}

class ErrorHandler {
  // 根据错误代码范围判断错误类型
  getErrorCategory(code: ErrorCode): string {
    if (code >= 100 && code < 200) {
      return "系统错误";
    } else if (code >= 200 && code < 300) {
      return "验证错误";
    } else if (code >= 300 && code < 400) {
      return "权限错误";
    } else if (code >= 400 && code < 500) {
      return "业务错误";
    }
    return "未知错误";
  }

  // 获取错误信息
  getErrorMessage(code: ErrorCode): string {
    const codeName = ErrorCode[code];
    const category = this.getErrorCategory(code);
    
    const messages: Record<ErrorCode, string> = {
      [ErrorCode.SystemError]: "系统内部错误",
      [ErrorCode.DatabaseError]: "数据库连接失败",
      [ErrorCode.NetworkError]: "网络连接异常",
      [ErrorCode.ValidationError]: "数据验证失败",
      [ErrorCode.InvalidInput]: "输入数据无效",
      [ErrorCode.MissingRequired]: "缺少必需字段",
      [ErrorCode.Unauthorized]: "未授权访问",
      [ErrorCode.Forbidden]: "禁止访问",
      [ErrorCode.TokenExpired]: "令牌已过期",
      [ErrorCode.NotFound]: "资源未找到",
      [ErrorCode.AlreadyExists]: "资源已存在",
      [ErrorCode.InsufficientFunds]: "余额不足"
    };

    return `[${category}] ${codeName}: ${messages[code]}`;
  }

  // 记录错误日志
  logError(code: ErrorCode, details?: any): void {
    const message = this.getErrorMessage(code);
    console.error(`错误代码 ${code}: ${message}`);
    if (details) {
      console.error("详细信息:", details);
    }
  }
}

const handler = new ErrorHandler();

handler.logError(ErrorCode.NotFound);
// 错误代码 400: [业务错误] NotFound: 资源未找到

handler.logError(ErrorCode.Unauthorized, { userId: 123 });
// 错误代码 300: [权限错误] Unauthorized: 未授权访问
// 详细信息: { userId: 123 }

// 使用反向映射获取所有错误代码
function getAllErrorCodes(): Array<{ code: number; name: string }> {
  return Object.keys(ErrorCode)
    .filter(key => isNaN(Number(key)))  // 过滤掉数字键(反向映射的键)
    .map(name => ({
      code: ErrorCode[name as keyof typeof ErrorCode] as number,
      name
    }));
}

console.log(getAllErrorCodes());
// [
//   { code: 100, name: 'SystemError' },
//   { code: 101, name: 'DatabaseError' },
//   ...
// ]

数字枚举 vs 字符串枚举

对比表格

特性数字枚举字符串枚举
成员值类型数字字符串
自动递增✅ 支持❌ 不支持
反向映射✅ 支持❌ 不支持
数值比较✅ 支持❌ 不支持
调试体验显示数字显示有意义的字符串
代码体积较大(包含反向映射)较小
类型安全可能接受任意数字更严格

选择建议

使用数字枚举的场景

  • 需要数值比较或范围判断(如 HTTP 状态码、优先级)
  • 需要反向映射功能(通过值获取名称)
  • 值本身是数字且有序(如状态机、步骤序列)
  • 需要利用数值进行计算或排序

使用字符串枚举的场景

  • 值本身是字符串标识符(如 API 端点、配置键)
  • 需要更好的调试体验(显示有意义的字符串)
  • 不需要数值比较或反向映射
  • 希望更严格的类型检查(不接受任意数字)

常见错误和注意事项

错误 1:混淆数字和字符串

typescript
enum Status {
  Pending,
  Processing,
  Completed
}

// ❌ 错误:不能将字符串赋值给数字枚举
const status: Status = "Pending";  // Error: Type '"Pending"' is not assignable to type 'Status'

// ✅ 正确:使用枚举成员
const status: Status = Status.Pending;

错误 2:字符串枚举不能自动递增

typescript
// ❌ 错误:字符串枚举必须为每个成员指定值
enum Color {
  Red,        // Error: Enum member must have initializer
  Green = "green",
  Blue = "blue"
}

// ✅ 正确:数字枚举可以自动递增
enum Status {
  Pending,    // 0
  Processing, // 1
  Completed   // 2
}

// ✅ 正确:字符串枚举必须显式指定值
enum Color {
  Red = "red",
  Green = "green",
  Blue = "blue"
}

错误 3:反向映射的陷阱

typescript
enum Status {
  Pending,
  Processing,
  Completed
}

// ⚠️ 注意:反向映射可能返回 undefined
console.log(Status[99]);  // undefined(如果值不存在)

// ✅ 安全的反向映射
function getStatusName(status: Status): string | undefined {
  return Status[status];
}

const name = getStatusName(Status.Pending);  // "Pending"
const invalid = getStatusName(99 as Status);  // undefined

注意事项

注意

  1. 类型检查的局限性:数字枚举在类型检查时可能不够严格,可能接受超出定义范围的数字值
  2. 编译后体积:数字枚举会生成反向映射,增加编译后的代码体积
  3. 值的选择:避免使用负数或过大的数字,可能导致意外的行为
  4. 部分指定值的风险:在枚举中间指定值时要小心,确保递增逻辑符合预期

提示

  1. 使用有意义的初始值:如果枚举值有特定含义(如 HTTP 状态码),使用对应的数值
  2. 保持一致性:要么全部自动递增,要么全部手动指定,避免混用
  3. 利用反向映射:在日志记录、错误处理等场景中充分利用反向映射
  4. 类型守卫:使用类型守卫确保枚举值的有效性

类型检查示例

类型安全的使用

typescript
enum Priority {
  Low = 1,
  Medium = 2,
  High = 3
}

// ✅ 正确:使用枚举类型
function setPriority(priority: Priority): void {
  console.log(`设置优先级: ${Priority[priority]}`);
}

setPriority(Priority.High);  // ✅ 正确

// ⚠️ 类型检查可能不够严格
setPriority(99 as Priority);  // 可以编译通过,但运行时可能有问题

// ✅ 使用类型守卫确保值有效
function isValidPriority(value: number): value is Priority {
  return Object.values(Priority)
    .filter(v => typeof v === 'number')
    .includes(value);
}

function setPrioritySafe(priority: number): void {
  if (isValidPriority(priority)) {
    console.log(`设置优先级: ${Priority[priority]}`);
  } else {
    console.error(`无效的优先级: ${priority}`);
  }
}

setPrioritySafe(Priority.High);  // ✅ "设置优先级: High"
setPrioritySafe(99);             // ✅ "无效的优先级: 99"

相关链接

基于 VitePress 构建