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