第 99 章:枚举(Enum)
学习目标
- 理解数字枚举、字符串枚举、异构枚举
- 学会 const enum 的性能优化
- 掌握枚举的替代方案(字面量联合)
- 了解枚举的使用场景与陷阱
一、什么是枚举
枚举(Enum)是把一组具名常量组织起来的方式。
typescript
// 没有枚举时的写法
const StatusPending = 0;
const StatusPaid = 1;
const StatusShipped = 2;
// 用枚举
enum Status {
Pending, // 0
Paid, // 1
Shipped // 2
}二、数字枚举
2.1 默认从 0 开始
typescript
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right // 3
}
console.log(Direction.Up); // 0
console.log(Direction[0]); // 'Up' (反向映射)2.2 自定义起始值
typescript
enum Status {
Pending = 1,
Paid, // 2
Shipped, // 3
}
enum HttpCode {
OK = 200,
NotFound = 404,
ServerError = 500
}2.3 跳跃式赋值
typescript
enum Power {
Low = 10,
Medium, // 11
High = 100,
Ultra // 101
}三、字符串枚举
typescript
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT'
}
// ❌ 数字枚举允许反向映射,字符串枚举不允许
console.log(Direction.Up); // 'UP'
console.log(Direction['UP']); // ❌ undefined字符串枚举优点:
- 调试时输出可读
- 序列化/反序列化更友好
- 避免数字混淆
typescript
// ✅ 推荐:业务语义用字符串枚举
enum OrderStatus {
Pending = 'PENDING',
Paid = 'PAID',
Shipped = 'SHIPPED',
Delivered = 'DELIVERED',
Cancelled = 'CANCELLED'
}
function updateOrder(status: OrderStatus) {
console.log(`订单状态:${status}`);
}
updateOrder(OrderStatus.Paid);
// 输出:订单状态:PAID四、异构枚举
混合数字和字符串,不推荐使用:
typescript
enum Mixed {
No = 0,
Yes = 'YES'
}企业级规范:禁止使用异构枚举,类型不一致会引发隐式转换 bug。
五、const enum(编译时优化)
typescript
const enum Direction {
Up,
Down,
Left,
Right
}
const d = Direction.Up;编译后:
javascript
// 普通 enum:生成对象
const Direction = { Up: 0, Down: 1, Left: 2, Right: 3 };
const d = Direction.Up;
// const enum:直接内联值
const d = 0; // 编译时替换5.1 优点
- ✅ 编译后没有对象,节省内存
- ✅ 减少运行时查找
5.2 缺点
- ❌ 不能跨模块用(isolatedModules 下报错)
- ❌ 不能在 d.ts 中使用(会展开失败)
5.3 何时用
typescript
// ✅ 用 const enum:纯本地、高频调用
const enum Direction { Up, Down, Left, Right }
// ❌ 用普通 enum:需要 d.ts 暴露给外部
enum ApiStatus { OK, Error }企业级规范:默认用普通 enum + 字面量联合;只在性能敏感处用 const enum。
六、枚举成员可以是函数吗?
枚举成员只能是基础类型(number、string、计算结果为常量的表达式):
typescript
enum Test {
A = 1 + 2, // ✅ 常量表达式
B = Math.random(), // ❌ Computed values are not permitted
C = 'hello'.length // ❌ Computed values are not permitted
}
// 编译时常量可以
enum FileAccess {
Read = 1 << 1, // 2
Write = 1 << 2, // 4
ReadWrite = Read | Write // 6
}七、枚举作为类型
7.1 当作联合类型
typescript
enum Status {
Active = 'ACTIVE',
Inactive = 'INACTIVE'
}
// ✅ 接受枚举的特定成员
function setStatus(s: Status.Active | Status.Inactive) {
// ...
}
setStatus(Status.Active); // ✅
setStatus(Status.Inactive); // ✅
// ❌ 数字/字符串直接传会被拒绝
// setStatus('ACTIVE'); // ❌7.2 反向访问的危险
typescript
enum Status {
Pending = 0,
Paid = 1
}
const status: number = 2; // 数字 2 在 Status 中没有定义
// 但 TS 不会报错(因为 Status 是数字)避免方式:用字符串枚举把"数字"和"枚举"区分开。
typescript
enum Status {
Pending = 'PENDING',
Paid = 'PAID'
}
const status: Status = 'ACTIVE'; // ❌ Type '"ACTIVE"' is not assignable to type 'Status'八、为什么不推荐枚举?
8.1 现代 TS 推荐字面量联合
typescript
// ❌ 用 enum(运行时存在)
enum OrderStatus {
Pending = 'PENDING',
Paid = 'PAID'
}
// ✅ 用字面量联合(推荐)
type OrderStatus = 'PENDING' | 'PAID';
function update(status: OrderStatus) {
// ...
}
update('PENDING'); // ✅8.2 对比
| 特性 | enum | 字面量联合 |
|---|---|---|
| 运行时存在 | ✅ 是 | ❌ 否 |
| 编译后大小 | 多了对象 | 零开销 |
| 反向映射 | 数字枚举有 | ❌ 无 |
| 调试可读性 | ✅ 字符串枚举好 | ❌ 需要查类型 |
| 现代化推荐 | 一般 | ✅ TS 5.x 推荐 |
8.3 真实场景建议
typescript
// ① 业务状态(团队内部)→ 用字面量联合
type Status = 'pending' | 'paid' | 'shipped';
// ② 第三方 API 强约束、JSON 序列化 → 用 enum
enum HttpStatus {
OK = 200,
NotFound = 404
}
// ③ 性能敏感的常量(如协议字段)→ const enum
const enum Flag { Read = 1, Write = 2 }九、枚举与 switch
typescript
enum Status {
Active = 'ACTIVE',
Inactive = 'INACTIVE',
Pending = 'PENDING'
}
function handle(status: Status) {
switch (status) {
case Status.Active:
return '活跃';
case Status.Inactive:
return '不活跃';
case Status.Pending:
return '待处理';
// 漏掉一个 case,TS 不会报错(enum 不支持穷尽性检查)
}
}对比字面量联合:
typescript
type Status = 'ACTIVE' | 'INACTIVE' | 'PENDING';
function handle(status: Status) {
switch (status) {
case 'ACTIVE':
return '活跃';
case 'INACTIVE':
return '不活跃';
case 'PENDING':
return '待处理';
default:
const _exhaustive: never = status; // ✅ 加新成员时这里会报错
return _exhaustive;
}
}十、实战案例
10.1 业务状态管理
typescript
// 订单状态
enum OrderStatus {
Draft = 'DRAFT',
Submitted = 'SUBMITTED',
Paid = 'PAID',
Shipped = 'SHIPPED',
Completed = 'COMPLETED',
Cancelled = 'CANCELLED'
}
class Order {
constructor(public id: number, public status: OrderStatus = OrderStatus.Draft) {}
submit() {
if (this.status !== OrderStatus.Draft) {
throw new Error('只有草稿状态可以提交');
}
this.status = OrderStatus.Submitted;
}
pay() {
if (this.status !== OrderStatus.Submitted) {
throw new Error('只有已提交状态可以支付');
}
this.status = OrderStatus.Paid;
}
}10.2 权限位运算
typescript
enum Permission {
Read = 1 << 0, // 1
Write = 1 << 1, // 2
Execute = 1 << 2, // 4
Admin = Read | Write | Execute // 7
}
const userPerm: Permission = Permission.Read | Permission.Write;
// 校验权限
function hasPermission(p: Permission, required: Permission): boolean {
return (p & required) === required;
}
hasPermission(userPerm, Permission.Read); // true
hasPermission(userPerm, Permission.Execute); // false十一、常见错误
11.1 枚举比较
typescript
enum A { X = 1 }
enum B { X = 2 }
const a: A = A.X;
const b: B = B.X;
console.log(a === b); // ❌ 编译错误:This comparison appears unintentional11.2 枚举成员相同值
typescript
enum A {
X = 1,
Y = 1 // ⚠️ 不会报错,但产生 reverse mapping 冲突
}11.3 从非枚举类型赋值
typescript
enum Status { Active = 1 }
const num: number = 1;
const s: Status = num; // ⚠️ 数字枚举允许,但实际可能不是合法值十二、本章小结
| 要点 | 关键 |
|---|---|
| 数字枚举 | 默认从 0 开始,支持反向映射 |
| 字符串枚举 | 推荐用于业务语义,无反向映射 |
| const enum | 编译时内联,性能好但有使用限制 |
| TS 5.x 推荐 | 优先用字面量联合,避免 enum |
| 何时用 enum | 第三方 API、JSON 序列化、强约束场景 |
动手练习
- 状态机:用数字枚举实现一个
TrafficLight(红/黄/绿),写一个切换函数 - 字面量替代:把练习 1 改成字面量联合,比较两种写法的优劣
- 权限系统:用位运算 enum 实现一套权限管理(Read/Write/Execute/Delete)
推荐阅读
- 📖 TypeScript Handbook - Enums — 官方枚举文档
- 📖 TypeScript 5.0 - Const Enum Changes — TS 5.0 enum 改进
- 🌐 《Enums areconsterous》 — 反对 enum 的观点
下一章:第 100 章:类型断言与类型守卫 →