Skip to content

枚举基础

概述

枚举(Enum)是 TypeScript 提供的一种特殊类型,用于定义一组命名的常量。枚举允许我们为一组相关的值赋予有意义的名称,提高代码的可读性和可维护性。枚举在 TypeScript 中既是一种类型,也是一个值,可以在运行时访问。理解枚举对于管理常量集合、状态码、配置选项等场景非常重要。

什么是枚举

枚举是一组具有名称的常量的集合。与直接使用数字或字符串相比,枚举提供了更好的类型安全性和代码可读性。

为什么使用枚举

使用枚举的主要优势:

  1. 类型安全:枚举提供了编译时的类型检查,避免使用无效的值
  2. 可读性:使用有意义的名称代替魔法数字或字符串
  3. 可维护性:集中管理常量,修改时只需在一处更新
  4. 智能提示:IDE 可以提供自动完成和类型提示
  5. 重构友好:重命名枚举成员时,所有引用会自动更新

枚举 vs 其他方式

typescript
// ❌ 不使用枚举:使用魔法数字
function setStatus(status: number): void {
  if (status === 0) {
    // 待处理
  } else if (status === 1) {
    // 处理中
  } else if (status === 2) {
    // 已完成
  }
}

setStatus(0);  // 不直观,容易出错
setStatus(99); // 无效值,但不会报错

// ❌ 不使用枚举:使用字符串常量
const STATUS_PENDING = "pending";
const STATUS_PROCESSING = "processing";
const STATUS_COMPLETED = "completed";

function setStatus(status: string): void {
  // ...
}

setStatus(STATUS_PENDING);  // 可以工作
setStatus("invalid");       // 无效值,但不会报错

// ✅ 使用枚举:类型安全且可读
enum Status {
  Pending,
  Processing,
  Completed
}

function setStatus(status: Status): void {
  // ...
}

setStatus(Status.Pending);  // ✅ 类型安全,可读性强
// setStatus(99);           // ❌ 编译错误:类型不匹配

基本语法

枚举声明

使用 enum 关键字声明枚举,枚举名使用 PascalCase(首字母大写的驼峰命名):

typescript
// 基本枚举声明
enum Direction {
  Up,
  Down,
  Left,
  Right
}

// 使用枚举
let direction: Direction = Direction.Up;
console.log(direction);  // 0(默认从 0 开始)

枚举成员访问

枚举成员可以通过枚举名和成员名访问:

typescript
enum Color {
  Red,
  Green,
  Blue
}

// 访问枚举成员
const primaryColor: Color = Color.Red;
const secondaryColor: Color = Color.Green;

// 枚举既是类型也是值
console.log(Color.Red);    // 0
console.log(Color.Green);  // 1
console.log(Color.Blue);   // 2

// 使用枚举作为类型
function setColor(color: Color): void {
  console.log(`Setting color to: ${color}`);
}

setColor(Color.Blue);  // "Setting color to: 2"

枚举的类型特性

枚举在 TypeScript 中具有双重身份:

  1. 作为类型:可以用于类型注解
  2. 作为值:可以在运行时访问
typescript
enum UserRole {
  Admin,
  User,
  Guest
}

// 作为类型使用
function checkPermission(role: UserRole): boolean {
  return role === UserRole.Admin;
}

// 作为值使用
const currentRole: UserRole = UserRole.Admin;
console.log(currentRole);  // 0

// 枚举成员的类型
const adminRole: UserRole.Admin = UserRole.Admin;  // 字面量类型

枚举类型

TypeScript 支持三种主要的枚举类型:

  1. 数字枚举(Numeric Enums):默认类型,成员值为数字
  2. 字符串枚举(String Enums):成员值为字符串
  3. 常量枚举(Const Enums):编译时内联的枚举

数字枚举

数字枚举是默认的枚举类型,成员值从 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 = 100,    // 100
  Processing = 200, // 200
  Completed = 300,  // 300
  Cancelled = 400   // 400
}

console.log(Status.Pending);    // 100
console.log(Status.Processing); // 200

// 部分指定值,后续成员自动递增
enum Status2 {
  Pending = 10,     // 10
  Processing,       // 11(自动递增)
  Completed,        // 12(自动递增)
  Cancelled = 20,   // 20
  Failed            // 21(自动递增)
}

字符串枚举

字符串枚举的每个成员都必须是字符串字面量:

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

console.log(Direction.Up);    // "UP"
console.log(Direction.Down); // "DOWN"

// 字符串枚举不能自动递增,必须为每个成员指定值
enum Color {
  Red = "red",
  Green = "green",
  Blue = "blue"
}

混合枚举(不推荐)

TypeScript 允许数字和字符串混合的枚举,但不推荐使用:

typescript
enum MixedEnum {
  No = 0,
  Yes = "yes"
}

// 不推荐:混合类型会导致类型检查困难

使用示例

示例 1:状态管理

使用枚举管理应用状态:

typescript
enum AppState {
  Loading,
  Ready,
  Error,
  Offline
}

class Application {
  private state: AppState = AppState.Loading;

  setState(newState: AppState): void {
    this.state = newState;
    this.handleStateChange();
  }

  private handleStateChange(): void {
    switch (this.state) {
      case AppState.Loading:
        console.log("应用正在加载...");
        break;
      case AppState.Ready:
        console.log("应用已就绪");
        break;
      case AppState.Error:
        console.log("应用发生错误");
        break;
      case AppState.Offline:
        console.log("应用处于离线状态");
        break;
    }
  }

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

const app = new Application();
app.setState(AppState.Loading);  // "应用正在加载..."
app.setState(AppState.Ready);       // "应用已就绪"

示例 2:HTTP 状态码

使用枚举定义 HTTP 状态码:

typescript
enum HttpStatus {
  OK = 200,
  Created = 201,
  Accepted = 202,
  NoContent = 204,
  BadRequest = 400,
  Unauthorized = 401,
  Forbidden = 403,
  NotFound = 404,
  InternalServerError = 500,
  BadGateway = 502,
  ServiceUnavailable = 503
}

function handleResponse(status: HttpStatus): void {
  if (status >= 200 && status < 300) {
    console.log("请求成功");
  } else if (status >= 400 && status < 500) {
    console.log("客户端错误");
  } else if (status >= 500) {
    console.log("服务器错误");
  }
}

handleResponse(HttpStatus.OK);                    // "请求成功"
handleResponse(HttpStatus.NotFound);              // "客户端错误"
handleResponse(HttpStatus.InternalServerError);   // "服务器错误"

示例 3:用户权限

使用字符串枚举定义用户权限:

typescript
enum Permission {
  Read = "read",
  Write = "write",
  Delete = "delete",
  Admin = "admin"
}

interface User {
  name: string;
  permissions: Permission[];
}

function hasPermission(user: User, permission: Permission): boolean {
  return user.permissions.includes(permission);
}

function canDelete(user: User): boolean {
  return hasPermission(user, Permission.Delete) || 
         hasPermission(user, Permission.Admin);
}

const user: User = {
  name: "Alice",
  permissions: [Permission.Read, Permission.Write]
};

console.log(hasPermission(user, Permission.Read));   // true
console.log(hasPermission(user, Permission.Delete));  // false
console.log(canDelete(user));                        // false

const admin: User = {
  name: "Bob",
  permissions: [Permission.Admin]
};

console.log(canDelete(admin));  // true

示例 4:配置选项

使用枚举定义配置选项:

typescript
enum LogLevel {
  Debug = "debug",
  Info = "info",
  Warn = "warn",
  Error = "error"
}

enum Environment {
  Development = "development",
  Staging = "staging",
  Production = "production"
}

interface AppConfig {
  logLevel: LogLevel;
  environment: Environment;
  port: number;
}

function createConfig(
  logLevel: LogLevel,
  environment: Environment
): AppConfig {
  return {
    logLevel,
    environment,
    port: environment === Environment.Production ? 443 : 3000
  };
}

const config = createConfig(LogLevel.Info, Environment.Production);
console.log(config);
// {
//   logLevel: "info",
//   environment: "production",
//   port: 443
// }

示例 5:方向控制

使用枚举定义方向:

typescript
enum Direction {
  North = "N",
  South = "S",
  East = "E",
  West = "W"
}

class Robot {
  private x: number = 0;
  private y: number = 0;
  private facing: Direction = Direction.North;

  move(direction: Direction, steps: number = 1): void {
    switch (direction) {
      case Direction.North:
        this.y += steps;
        break;
      case Direction.South:
        this.y -= steps;
        break;
      case Direction.East:
        this.x += steps;
        break;
      case Direction.West:
        this.x -= steps;
        break;
    }
    this.facing = direction;
  }

  getPosition(): { x: number; y: number; facing: Direction } {
    return { x: this.x, y: this.y, facing: this.facing };
  }
}

const robot = new Robot();
robot.move(Direction.North, 5);
robot.move(Direction.East, 3);
console.log(robot.getPosition());
// { x: 3, y: 5, facing: "E" }

枚举的反向映射

数字枚举具有反向映射特性,可以通过值获取枚举成员名:

typescript
enum Status {
  Pending,
  Processing,
  Completed
}

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

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

// 使用反向映射
function getStatusName(status: Status): string {
  return Status[status];
}

console.log(getStatusName(Status.Pending));  // "Pending"

注意

字符串枚举没有反向映射特性。只有数字枚举支持反向映射。

类型检查示例

常见错误

typescript
enum Status {
  Pending,
  Processing,
  Completed
}

// ❌ 错误:类型不匹配
function setStatus(status: Status): void {
  // ...
}

setStatus(0);  // Error: Argument of type '0' is not assignable to parameter of type 'Status'
setStatus("Pending");  // Error: Argument of type '"Pending"' is not assignable to parameter of type 'Status'

// ❌ 错误:访问不存在的枚举成员
console.log(Status.Invalid);  // Error: Property 'Invalid' does not exist on type 'typeof Status'

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

正确写法

typescript
enum Status {
  Pending,
  Processing,
  Completed
}

// ✅ 正确:使用枚举成员
function setStatus(status: Status): void {
  // ...
}

setStatus(Status.Pending);      // ✅ 正确
setStatus(Status.Processing);  // ✅ 正确

// ✅ 正确:使用类型断言(不推荐,但可行)
setStatus(0 as Status);  // 可以工作,但不推荐

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

// ✅ 正确:数字枚举可以部分指定值
enum Status2 {
  Pending = 10,
  Processing,  // 自动为 11
  Completed    // 自动为 12
}

注意事项

提示

  • 枚举提供了类型安全和代码可读性
  • 使用枚举代替魔法数字和字符串常量
  • 枚举成员名使用 PascalCase(首字母大写的驼峰命名)
  • 数字枚举支持反向映射,可以通过值获取名称
  • 字符串枚举提供更好的调试体验(显示有意义的字符串)
  • 枚举既是类型也是值,可以在运行时访问

注意

  • 字符串枚举的每个成员都必须显式指定值
  • 字符串枚举不支持反向映射
  • 混合枚举(数字和字符串)不推荐使用
  • 枚举在编译后会生成 JavaScript 对象,增加代码体积
  • 数字枚举的值可以超出定义的成员范围(类型检查可能不够严格)

重要

  • 枚举是 TypeScript 特有的特性,编译后会生成 JavaScript 代码
  • 如果只需要类型检查而不需要运行时值,考虑使用联合类型(Union Types)
  • 对于大型项目,枚举会增加 JavaScript 代码体积,需要权衡使用
  • 枚举成员的值在编译时确定,不能动态计算(除了引用其他枚举成员)

信息

枚举的应用场景

  1. 状态管理:应用状态、请求状态、订单状态等

    • enum AppState { Loading, Ready, Error }
    • enum OrderStatus { Pending, Processing, Completed }
  2. 配置选项:日志级别、环境配置、主题设置等

    • enum LogLevel { Debug, Info, Warn, Error }
    • enum Environment { Dev, Staging, Prod }
  3. 常量集合:HTTP 状态码、错误代码、权限类型等

    • enum HttpStatus { OK = 200, NotFound = 404 }
    • enum Permission { Read, Write, Admin }
  4. 方向/位置:方向控制、坐标系统等

    • enum Direction { North, South, East, West }
  5. 类型标识:区分不同的数据类型或变体

    • enum UserType { Admin, User, Guest }

枚举 vs 联合类型

特性枚举联合类型
运行时存在✅ 是❌ 否(编译时擦除)
反向映射✅ 数字枚举支持❌ 不支持
代码体积增加不增加
类型安全
智能提示
适用场景需要运行时值仅需类型检查

选择建议

  • 如果需要运行时访问枚举值,使用枚举
  • 如果只需要类型检查,考虑使用联合类型
  • 如果需要反向映射,使用数字枚举
  • 如果需要更好的调试体验,使用字符串枚举

相关链接

基于 VitePress 构建