数字枚举
概述
数字枚举(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.Pending→0Status[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注意事项
注意
- 类型检查的局限性:数字枚举在类型检查时可能不够严格,可能接受超出定义范围的数字值
- 编译后体积:数字枚举会生成反向映射,增加编译后的代码体积
- 值的选择:避免使用负数或过大的数字,可能导致意外的行为
- 部分指定值的风险:在枚举中间指定值时要小心,确保递增逻辑符合预期
提示
- 使用有意义的初始值:如果枚举值有特定含义(如 HTTP 状态码),使用对应的数值
- 保持一致性:要么全部自动递增,要么全部手动指定,避免混用
- 利用反向映射:在日志记录、错误处理等场景中充分利用反向映射
- 类型守卫:使用类型守卫确保枚举值的有效性
类型检查示例
类型安全的使用
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"