强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

TypeScript 开发指南 / 16 - 枚举

枚举

枚举(Enum)是 TypeScript 特有的特性,用于定义一组命名常量。

数字枚举(Numeric Enums)

// 基本数字枚举
enum Direction {
  Up,      // 0
  Down,    // 1
  Left,    // 2
  Right    // 3
}

// 使用
const dir = Direction.Up;
console.log(dir);           // 0
console.log(Direction[0]);  // "Up"(反向映射)

// 自定义起始值
enum HttpStatus {
  OK = 200,
  Created = 201,
  BadRequest = 400,
  Unauthorized = 401,
  NotFound = 404,
  InternalServerError = 500
}

// 自动递增
enum Priority {
  Low = 1,
  Medium,    // 2
  High,      // 3
  Critical   // 4
}

数字枚举的特性

enum Color {
  Red,    // 0
  Green,  // 1
  Blue    // 2
}

// 正向映射
console.log(Color.Red);      // 0
console.log(Color["Red"]);   // 0

// 反向映射(只有数字枚举支持)
console.log(Color[0]);       // "Red"
console.log(Color[1]);       // "Green"

// 类型
const c: Color = Color.Red;  // ✅
const d: Color = 0;          // ✅ 数字枚举是数字的子类型
const e: Color = 3;          // ✅ 任何数字都兼容

注意:数字枚举允许任何数字赋值,这可能导致类型不安全。字符串枚举更安全。

字符串枚举(String Enums)

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

const dir = Direction.Up;
console.log(dir); // "UP"

// 没有反向映射
// console.log(Direction["UP"]); // undefined

// 更安全的类型
const d: Direction = Direction.Up;  // ✅
// const e: Direction = "UP";       // ❌ 不能直接赋值字符串

字符串枚举 vs 数字枚举

特性数字枚举字符串枚举
默认值0, 1, 2…必须显式赋值
反向映射
运行时值数字字符串
类型安全❌(允许任意数字)
序列化可读性差可读性好
性能略好相同

const 枚举

const 枚举在编译时会被完全内联,不生成额外代码:

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

const dir = Direction.Up;
// 编译后:const dir = "UP"(直接内联,没有 Direction 对象)

// ❌ 限制
// 不能访问枚举对象
// const directions = Object.values(Direction); // Error

const 枚举 vs 普通枚举

// 普通枚举:编译后生成对象
enum RegularEnum {
  A = 1,
  B = 2
}
// 编译输出:
// var RegularEnum;
// (function (RegularEnum) {
//     RegularEnum[RegularEnum["A"] = 1] = "A";
//     RegularEnum[RegularEnum["B"] = 2] = "B";
// })(RegularEnum || (RegularEnum = {}));

// const 枚举:编译后内联
const enum ConstEnum {
  A = 1,
  B = 2
}
const value = ConstEnum.A;
// 编译输出:const value = 1;
特性普通枚举const 枚举
编译输出生成对象代码内联值
反向映射✅(数字枚举)
运行时访问
文件大小较大较小
适用场景需要运行时枚举性能敏感

异构枚举(Heterogeneous Enums)

// 混合数字和字符串(不推荐)
enum Mixed {
  No = 0,
  Yes = "YES"
}

注意:异构枚举不推荐使用,容易造成混淆。

枚举成员

常量成员

enum FileAccess {
  // 数字常量
  None = 0,
  Read = 1,
  Write = 2,
  ReadWrite = Read | Write,  // 位运算

  // 字符串常量
  FileName = "file.txt",

  // 计算成员(编译时求值)
  Max = 1 << 2,               // 4
  Mask = Read | Write | 4     // 7
}

计算成员

enum Computed {
  A = "hello".length,       // 运行时计算
  B = 1 + 2,               // 编译时计算
  C = Math.random()         // 运行时计算
}

枚举合并

// 同名枚举会自动合并
enum Animal {
  Dog = "DOG",
  Cat = "CAT"
}

enum Animal {
  Bird = "BIRD",
  Fish = "FISH"
}

// 最终 Animal 有所有成员
const pet: Animal = Animal.Dog;
console.log(Animal.Bird); // "BIRD"

枚举作为类型

enum Status {
  Active = "ACTIVE",
  Inactive = "INACTIVE",
  Banned = "BANNED"
}

// 作为变量类型
const userStatus: Status = Status.Active;

// 作为函数参数类型
function setStatus(userId: number, status: Status): void {
  // ...
}

setStatus(1, Status.Active);    // ✅
// setStatus(1, "ACTIVE");      // ❌
// setStatus(1, "active");      // ❌

// 作为接口属性类型
interface User {
  id: number;
  name: string;
  status: Status;
}

枚举替代方案

在许多场景下,联合类型比枚举更好:

联合字面量类型

// 使用联合类型替代枚举
type Status = "active" | "inactive" | "banned";

const status: Status = "active";

// 好处:更轻量,不需要运行时对象
// 缺点:不能反向映射

const 对象

// 使用 as const 对象替代枚举
const Direction = {
  Up: "UP",
  Down: "DOWN",
  Left: "LEFT",
  Right: "RIGHT"
} as const;

type Direction = typeof Direction[keyof typeof Direction];
// "UP" | "DOWN" | "LEFT" | "RIGHT"

const dir: Direction = Direction.Up; // ✅

枚举 vs 联合类型 vs const 对象

特性枚举联合类型const 对象
运行时对象
反向映射✅(数字)
Tree-shaking不完全
代码生成较多
类型安全一般最好
迁移性独立需定义独立

枚举的高级用法

位标志枚举

enum Permission {
  None = 0,
  Read = 1 << 0,     // 1
  Write = 1 << 1,    // 2
  Execute = 1 << 2,  // 4
  Admin = 1 << 3     // 8
}

// 组合权限
const userPermission = Permission.Read | Permission.Write;

// 检查权限
function hasPermission(userPerm: Permission, check: Permission): boolean {
  return (userPerm & check) === check;
}

console.log(hasPermission(userPermission, Permission.Read));   // true
console.log(hasPermission(userPermission, Permission.Admin));  // false

// 添加权限
function addPermission(userPerm: Permission, add: Permission): Permission {
  return userPerm | add;
}

// 移除权限
function removePermission(userPerm: Permission, remove: Permission): Permission {
  return userPerm & ~remove;
}

枚举映射

enum Color {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE"
}

// 枚举到值的映射
const colorHex: Record<Color, string> = {
  [Color.Red]: "#FF0000",
  [Color.Green]: "#00FF00",
  [Color.Blue]: "#0000FF"
};

// 值到枚举的映射
function toColor(hex: string): Color | undefined {
  const entry = Object.entries(colorHex).find(([_, v]) => v === hex);
  return entry ? (entry[0] as Color) : undefined;
}

业务场景:订单状态机

enum OrderStatus {
  Pending = "PENDING",
  Paid = "PAID",
  Processing = "PROCESSING",
  Shipped = "SHIPPED",
  Delivered = "DELIVERED",
  Cancelled = "CANCELLED",
  Refunded = "REFUNDED"
}

// 状态转换规则
const validTransitions: Record<OrderStatus, OrderStatus[]> = {
  [OrderStatus.Pending]: [OrderStatus.Paid, OrderStatus.Cancelled],
  [OrderStatus.Paid]: [OrderStatus.Processing, OrderStatus.Cancelled],
  [OrderStatus.Processing]: [OrderStatus.Shipped, OrderStatus.Cancelled],
  [OrderStatus.Shipped]: [OrderStatus.Delivered],
  [OrderStatus.Delivered]: [OrderStatus.Refunded],
  [OrderStatus.Cancelled]: [],
  [OrderStatus.Refunded]: []
};

function canTransition(
  from: OrderStatus,
  to: OrderStatus
): boolean {
  return validTransitions[from].includes(to);
}

function transitionOrder(
  current: OrderStatus,
  next: OrderStatus
): OrderStatus {
  if (!canTransition(current, next)) {
    throw new Error(
      `Cannot transition from ${current} to ${next}`
    );
  }
  return next;
}

// 使用
let orderStatus = OrderStatus.Pending;
orderStatus = transitionOrder(orderStatus, OrderStatus.Paid);
orderStatus = transitionOrder(orderStatus, OrderStatus.Processing);
// orderStatus = transitionOrder(orderStatus, OrderStatus.Delivered);
// Error: Cannot transition from PROCESSING to DELIVERED

注意事项

  1. 优先考虑联合类型——除非需要反向映射或运行时枚举对象
  2. const 枚举适合性能敏感场景,但不能在运行时访问枚举对象
  3. 字符串枚举比数字枚举更安全——不允许任意值赋值
  4. 避免异构枚举——混合数字和字符串容易混淆
  5. 枚举合并可以跨文件扩展枚举,但不推荐过度使用

扩展阅读