Typescript 枚举使用技巧

22 阅读9分钟

1. 基础枚举类型

数值枚举(默认)

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

// 自定义起始值
enum Status {
  Pending = 1,
  Approved,    // 2
  Rejected     // 3
}

字符串枚举

enum Message {
  Success = "SUCCESS",
  Error = "ERROR",
  Warning = "WARNING"
}

// 运行时保留为字符串,提供更好的可读性
console.log(Message.Success); // "SUCCESS"

异构枚举(混合)

enum MixedEnum {
  No = 0,
  Yes = "YES",
  Maybe = 2  // 需要手动指定
}

2. 常量枚举(性能优化)

// 编译时会被完全移除,减少运行时开销
const enum Colors {
  Red = "#FF0000",
  Green = "#00FF00",
  Blue = "#0000FF"
}

// 编译后:const color = "#FF0000";
const color = Colors.Red;

// ⚠️ 注意:常量枚举不能有计算成员
const enum Size {
  Small = getSize(), // 错误:计算成员
  Medium = 2
}

3. 计算成员和常量成员

enum FileAccess {
  // 常量成员
  None,
  Read = 1 << 1,    // 2
  Write = 1 << 2,   // 4
  ReadWrite = Read | Write, // 6
  
  // 计算成员(运行时计算)
  Computed = "file".length, // 4
  
  // 计算成员后的成员必须初始化
  Another = Computed + 1 // 5
}

4. 反向映射(数值枚举特有)

enum Weekday {
  Monday = 1,
  Tuesday,
  Wednesday
}

// 正向访问
console.log(Weekday.Monday); // 1

// 反向访问(只适用于数值枚举)
console.log(Weekday[1]); // "Monday"
console.log(Weekday[Weekday.Monday]); // "Monday"

// 字符串枚举没有反向映射
enum StringEnum {
  A = "a"
}
console.log(StringEnum["a"]); // undefined ❌

5. 联合枚举类型

enum ShapeKind {
  Circle,
  Square,
}

interface Circle {
  kind: ShapeKind.Circle;  // 类型为 ShapeKind.Circle (0)
  radius: number;
}

interface Square {
  kind: ShapeKind.Square;  // 类型为 ShapeKind.Square (1)
  sideLength: number;
}

// 类型保护:TypeScript 知道 kind 只能是特定值
function getArea(shape: Circle | Square) {
  switch (shape.kind) {
    case ShapeKind.Circle:
      // 这里 shape 被推断为 Circle
      return Math.PI * shape.radius ** 2;
    case ShapeKind.Square:
      // 这里 shape 被推断为 Square
      return shape.sideLength ** 2;
  }
}

6. 枚举作为对象使用

// 通过 namespace 扩展枚举
enum LogLevel {
  Error,
  Warning,
  Info,
  Debug
}

namespace LogLevel {
  // 添加辅助方法
  export function getColor(level: LogLevel): string {
    switch (level) {
      case LogLevel.Error: return "red";
      case LogLevel.Warning: return "yellow";
      case LogLevel.Info: return "blue";
      case LogLevel.Debug: return "gray";
    }
  }
  
  // 添加属性
  export const prefix = "[LOG]";
}

console.log(LogLevel.getColor(LogLevel.Error)); // "red"

7. 运行时技巧

遍历枚举

enum Fruit {
  Apple,
  Banana,
  Orange
}

// 获取所有键
const keys = Object.keys(Fruit).filter(k => isNaN(Number(k)));
// ["Apple", "Banana", "Orange"]

// 获取所有值
const values = Object.values(Fruit).filter(v => typeof v === "number");
// [0, 1, 2]

// 安全遍历
function getEnumValues<T extends object>(enumObj: T): Array<T[keyof T]> {
  return Object.values(enumObj).filter(
    value => typeof value === "number" || typeof value === "string"
  ) as Array<T[keyof T]>;
}

枚举检查

enum UserRole {
  Admin = "admin",
  User = "user",
  Guest = "guest"
}

// 检查值是否为有效枚举值
function isValidRole(role: string): role is UserRole {
  return Object.values(UserRole).includes(role as UserRole);
}

// 类型安全的获取
function getRole(key: string): UserRole | undefined {
  return Object.values(UserRole).find(value => value === key);
}

8. 高级模式

标志位枚举(Flags)

enum Permissions {
  None = 0,
  Read = 1 << 0,   // 1
  Write = 1 << 1,  // 2
  Delete = 1 << 2, // 4
  Execute = 1 << 3, // 8
  All = Read | Write | Delete | Execute // 15
}

// 使用
const userPermissions = Permissions.Read | Permissions.Write; // 3

// 检查权限
function hasPermission(userPerms: Permissions, required: Permissions): boolean {
  return (userPerms & required) === required;
}

// 添加权限
function addPermission(userPerms: Permissions, perm: Permissions): Permissions {
  return userPerms | perm;
}

枚举映射

// 创建类型安全的映射
enum EventType {
  Click = "click",
  Hover = "hover",
  Focus = "focus"
}

type EventHandlers = {
  [K in EventType]: () => void;
};

const handlers: EventHandlers = {
  [EventType.Click]: () => console.log("clicked"),
  [EventType.Hover]: () => console.log("hovered"),
  [EventType.Focus]: () => console.log("focused")
};

9. 最佳实践

使用 const enum 优化性能

// 生产代码中使用 const enum
const enum ApiStatus {
  Loading,
  Success,
  Error
}

// 开发调试时使用普通 enum
// enum ApiStatus { ... }

避免魔法数字/字符串

// ❌ 不好
if (status === 1) { /* ... */ }

// ✅ 好
enum TaskStatus {
  Pending = 1,
  InProgress = 2,
  Completed = 3
}
if (status === TaskStatus.InProgress) { /* ... */ }

使用字符串枚举提高可读性

// ❌ 数值枚举在日志中难以理解
console.log(ErrorType[1]); // 需要查文档

// ✅ 字符串枚举直接可读
enum ErrorType {
  NotFound = "NOT_FOUND",
  Unauthorized = "UNAUTHORIZED"
}
console.log(ErrorType.NotFound); // "NOT_FOUND"

10. 常见问题解决

枚举值类型安全

// 防止无效值
enum Color {
  Red = "red",
  Green = "green",
  Blue = "blue"
}

// 使用类型守卫
function isColor(value: string): value is Color {
  return Object.values(Color).includes(value as Color);
}

// 运行时验证
function parseColor(input: string): Color {
  if (isColor(input)) {
    return input;
  }
  throw new Error(`Invalid color: ${input}`);
}

枚举编译问题

// 如果枚举值需要用在计算属性中,不能使用 const enum
const enum Size {
  Small = 1,
  Medium,
  Large
}

// ❌ 错误:const enum 在运行时不存在
const sizes = [Size.Small, Size.Medium, Size.Large];

// ✅ 改用普通 enum
enum Size {
  Small = 1,
  Medium,
  Large
}

总结对比表

特性数值枚举字符串枚举const enum
反向映射✅ 有❌ 无✅ 编译时移除
运行时存在✅ 是✅ 是❌ 否
可读性一般优秀一般
性能普通普通优秀
使用场景标志位、状态码配置、错误码性能敏感场景

选择建议:

  • 需要反向映射 → 数值枚举
  • 需要可读性和调试方便 → 字符串枚举
  • 性能优先,编译后不需要枚举 → const enum
  • 需要位运算 → 数值枚举

枚举中的计算成员和常量成员

TypeScript 枚举中的计算成员和常量成员

1. 基本概念

枚举成员分为两种:

  • 常量成员:在编译阶段就能确定值
  • 计算成员:在运行时才能确定值
enum Example {
  // 常量成员
  A,                    // 0(默认)
  B = 1,               // 1(字面量)
  C = 1 + 2,           // 3(常量表达式)
  D = 1 << 2,          // 4(位运算)
  
  // 计算成员
  E = Math.random(),   // 运行时计算
  F = "hello".length,  // 运行时计算
  G = parseInt("123")  // 运行时计算
}

2. 常量成员详解

2.1 隐式常量成员

enum Direction {
  Up,     // 0(第一个成员,默认0)
  Down,   // 1(前一个成员+1)
  Left,   // 2
  Right   // 3
}

// 编译后的 JavaScript
var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Down"] = 1] = "Down";
    Direction[Direction["Left"] = 2] = "Left";
    Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));

2.2 显式常量成员

enum Status {
  Pending = 10,        // 显式设置为10
  InProgress,          // 11(自动递增)
  Completed = 20,      // 显式设置为20
  Cancelled            // 21(自动递增)
}

2.3 常量表达式成员

enum Flags {
  // 这些都是常量成员(编译时计算)
  A = 1 + 2,           // 3
  B = 3 * 4,           // 12
  C = 1 << 3,          // 8(位运算)
  D = 0b1010,          // 10(二进制)
  E = 0o12,            // 10(八进制)
  F = 0xA,             // 10(十六进制)
  G = ~1,              // -2(位运算取反)
  H = 10 | 5,          // 15(位运算或)
}

3. 计算成员详解

3.1 什么是计算成员

计算成员的值在运行时才能确定:

enum ComputedExample {
  // 这些都是在运行时计算的
  Length = "hello".length,      // 5
  Random = Math.random(),       // 随机数
  Timestamp = Date.now(),       // 当前时间戳
  Parsed = parseInt("100px"),   // 100
  PI = Math.PI,                 // 3.141592653589793
  MAX = Number.MAX_SAFE_INTEGER // 9007199254740991
}

3.2 计算成员的约束

enum MixedEnum {
  // 常量成员
  A = 1,
  B = A + 1,           // ✅ 可以引用之前的常量成员
  
  // 计算成员
  C = "text".length,   // 4
  
  // ❌ 错误:计算成员后的成员必须显式初始化
  D,                   // Error: Enum member must have initializer
  
  // ✅ 必须显式初始化
  E = C + 1,           // 5(计算成员)
  F = 10               // 10(常量成员)
}

4. 常量 vs 计算成员的判断规则

判断流程图

成员初始化值
    ↓
是否字面量(数字/字符串)? → 是 → 常量成员
    ↓ 否
是否一元运算符? → 是 → 常量成员
    ↓ 否  
是否二元运算符? → 是 → 两边都是常量? → 是 → 常量成员
    ↓ 否                       ↓ 否
是否是特定全局对象? → 是 → 计算成员
    ↓ 否
是否是特定函数调用? → 是 → 计算成员
    ↓ 否
其他情况 → 计算成员

具体判断规则

enum Test {
  // ✅ 常量成员(字面量)
  A = 1,
  B = "hello",
  
  // ✅ 常量成员(常量表达式)
  C = 1 + 2,
  D = -1,              // 一元运算符
  E = ~0,              // 位运算取反
  F = 1 << 3,          // 位运算
  G = 2 ** 3,          // 幂运算
  
  // ✅ 常量成员(引用之前的常量成员)
  H = A + 1,           // 2
  I = B.length,        // 5(字符串字面量的属性访问)
  
  // ❌ 计算成员(运行时计算)
  J = "hello".length,  // 字符串对象方法调用
  K = Math.random(),   // 数学函数调用
  L = Date.now(),      // 日期函数调用
  M = parseInt("123"), // 全局函数调用
  
  // ❌ 计算成员后的成员必须初始化
  // N,                 // 错误!
  O = 10               // 必须显式初始化
}

5. 实际应用场景

场景1:配置相关枚举

enum ApiConfig {
  // 常量成员
  TIMEOUT = 5000,
  RETRY_TIMES = 3,
  
  // 计算成员(基于常量)
  TOTAL_TIMEOUT = TIMEOUT * RETRY_TIMES,  // 15000
  
  // 字符串计算
  BASE_URL = "https://api.example.com",
  AUTH_ENDPOINT = BASE_URL + "/auth"      // ✅ 常量表达式
}

enum DynamicConfig {
  // 运行时配置
  CURRENT_YEAR = new Date().getFullYear(),  // 计算成员
  RANDOM_SEED = Math.floor(Math.random() * 1000),
  
  // ⚠️ 后面的成员必须显式初始化
  NEXT_YEAR = CURRENT_YEAR + 1
}

场景2:权限标志位

enum Permission {
  // 常量成员(位标志)
  NONE = 0,
  READ = 1 << 0,      // 1
  WRITE = 1 << 1,     // 2
  DELETE = 1 << 2,    // 4
  EXECUTE = 1 << 3,   // 8
  
  // 计算成员(组合权限)
  READ_WRITE = READ | WRITE,      // 3
  ALL = READ | WRITE | DELETE | EXECUTE,  // 15
  
  // 动态权限(基于环境)
  IS_ADMIN = process.env.IS_ADMIN === 'true' ? ALL : READ  // 计算成员
}

场景3:状态管理

enum LoadingState {
  // 常量成员
  IDLE = "idle",
  LOADING = "loading",
  
  // 计算成员(基于环境或配置)
  TIMEOUT_DURATION = parseInt(process.env.TIMEOUT || "30000"),
  
  // 错误状态(动态生成)
  ERROR_PREFIX = "error_",
  NETWORK_ERROR = ERROR_PREFIX + "network",  // "error_network"
  SERVER_ERROR = ERROR_PREFIX + "server"     // "error_server"
}

6. 重要注意事项

6.1 const enum 的限制

// ❌ 错误:const enum 不能有计算成员
const enum BadEnum {
  A = "hello".length  // Error: const enum member initializers must be constant expressions
}

// ✅ 正确:只包含常量成员
const enum GoodEnum {
  A = 1,
  B = A + 1,        // ✅ 引用常量成员
  C = 1 << 2        // ✅ 常量表达式
}

6.2 计算成员后的初始化

enum Example {
  A = 1,
  B = "test".length,  // 计算成员
  // C,               // ❌ 错误:必须初始化
  D = 10,             // ✅ 显式初始化
  E = A + D           // ✅ 11(引用前面的常量成员)
}

6.3 类型安全

enum Mixed {
  NumberVal = 1,
  StringVal = "hello",
  ComputedVal = Math.random()
}

// 类型推断
type ValueType = typeof Mixed[keyof typeof Mixed];
// 相当于:1 | "hello" | number

function handleValue(val: ValueType) {
  // val 可以是 1、"hello" 或任意 number
}

7. 性能考虑

编译时优化

// 常量成员在编译时被替换
enum Constants {
  PI = 3.14159,
  E = 2.71828,
  ANSWER = 42
}

// 编译后:直接使用字面量
const area = 3.14159 * radius * radius;  // PI 被替换

// 计算成员在运行时计算
enum RuntimeValues {
  RANDOM = Math.random(),
  TIMESTAMP = Date.now()
}

// 编译后:保留函数调用
const random = Math.random();  // 每次调用都重新计算

8. 最佳实践

8.1 明确区分用途

// ✅ 清晰的设计
enum AppConstants {
  // 配置常量(编译时确定)
  VERSION = "1.0.0",
  MAX_ITEMS = 100,
  TIMEOUT_MS = 5000,
  
  // 计算值(运行时确定)
  START_TIME = Date.now(),
  RANDOM_ID = Math.random().toString(36).substr(2),
  
  // 基于配置的计算
  API_URL = process.env.API_URL || "http://localhost:3000"
}

8.2 避免混用模式

// ❌ 避免:混用模式导致困惑
enum Confusing {
  A = 1,
  B = "string",
  C = A + 2,          // 数字计算
  D = B + " suffix",  // 字符串拼接
  E = Math.random()   // 随机数
}

// ✅ 清晰:分开定义
enum NumericValues {
  DEFAULT_TIMEOUT = 3000,
  MAX_RETRIES = 3,
  BACKOFF_MULTIPLIER = 2
}

enum StringValues {
  STATUS_SUCCESS = "success",
  STATUS_ERROR = "error",
  STATUS_PENDING = "pending"
}

enum RuntimeValues {
  CURRENT_TIMESTAMP = Date.now(),
  SESSION_ID = generateSessionId()
}

9. 调试技巧

enum DebugExample {
  STATIC_VALUE = 100,
  COMPUTED_VALUE = Math.random(),
  
  // 添加调试信息
  DEBUG_INFO = (() => {
    console.log("枚举初始化时计算");
    return "debug";
  })()
}

// 查看枚举结构
console.log(DebugExample);
console.log(Object.keys(DebugExample));
console.log(Object.values(DebugExample));

// 类型检查
type Keys = keyof typeof DebugExample;  // "STATIC_VALUE" | "COMPUTED_VALUE" | "DEBUG_INFO"
type Values = typeof DebugExample[Keys]; // 100 | number | "debug"

总结

特性常量成员计算成员
确定时机编译时运行时
允许的操作字面量、常量表达式、位运算函数调用、方法调用、变量引用
const enum✅ 支持❌ 不支持
性能编译时优化,运行时无开销运行时计算,有性能开销
反向映射✅ 数值枚举支持❌ 不支持
自动递增✅ 支持❌ 不支持
初始化要求无特殊要求之后的成员必须显式初始化

使用建议:

  1. 优先使用常量成员,除非必要才用计算成员
  2. 需要性能优化时使用 const enum(只含常量成员)
  3. 计算成员后一定要显式初始化后续成员
  4. 避免在计算成员中使用复杂的运行时逻辑