TypeScript Enum 还有用吗?
TS 5.0+ 起 enum 已经从"首选"变成"备选"——能 as const 对象就别 enum。
- 三种 enum 类型:数字(自动递增 + 反向映射)、字符串、异构(不推荐)
- 数字 enum 的坑:编译后是 JS 对象,无法 tree-shake,反向映射易踩坑
- 字符串 enum:tree-shake 友好,但运行时仍有开销
- const enum:跟
isolatedModules冲突,TS 5.0 起不再推荐 - 首选替代:
as const对象 + 联合类型,零运行时开销
enum 在 2026 年的地位
TS 团队在 5.0 之后基本不再推 enum。原因是它把"枚举"这个纯类型层级的概念强行塞进了运行时——数字 enum 编译后是带反向映射的 JS 对象,字符串 enum 编译后是一堆赋值语句。两者都让 tree-shaking 失效,bundle 里总会带上整个 enum 表。
现代推荐:用 as const 对象 + 联合类型替代 enum。下面会讲清 enum 的细节,再讲为什么替代品更好。
三种 enum 类型
// 数字 enum:自动递增 + 反向映射
enum Direction {
Up, // 0
Down, // 1
Left, // 2
Right, // 3
}
// 字符串 enum:每个成员必须显式赋值
enum Status {
Active = 'ACTIVE',
Inactive = 'INACTIVE',
}
// 异构 enum:数字和字符串混着来
enum Mixed {
No = 0,
Yes = 'YES',
}
注意:异构 enum 在 TypeScript 官方手册 里就被标了"几乎从不需要",别用。
数字 enum 与反向映射
数字 enum 默认从 0 开始递增,可以手动赋初值打断递增。但有 两个坑:
坑 1:手动赋值较小会被覆盖
enum Days {
Sun = 3,
Mon = 1,
Tue, // 2
// ...
Wed, // 3(和 Sun 撞了)
}
Days[3] === 'Sun'; // false
Days[3] === 'Wed'; // true(后定义的覆盖)
坑 2:反向映射是个 JS 对象
数字 enum 编译后是双向键值对:
var Days;
(function (Days) {
Days[(Days['Sun'] = 0)] = 'Sun';
Days[(Days['Mon'] = 1)] = 'Mon';
})(Days || (Days = {}));
这就导致 for...in Days 会同时遍历出 key 和 value 两个版本:
for (const k in Days) {
console.log(k); // '0', '1', ..., 'Sun', 'Mon', ...
}
最佳实践:遍历数字 enum 时用 Object.keys(Days).filter(k => isNaN(Number(k))) 过滤出字符串 key。
字符串 enum:tree-shake 友好但仍有运行时开销
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}
字符串 enum 没有反向映射——只生成 Direction['Up'] = 'UP' 这种单向赋值。tree-shaker 能识别出哪些引用没用到,把没用的成员删掉。
但它仍然是运行时对象,没 as const 那么彻底。
const enum 跟 isolatedModules 冲突
const enum 的本意是编译时直接内联,省掉运行时对象:
const enum Direction {
Up = 'UP',
Down = 'DOWN',
}
const d = Direction.Up; // 编译后 const d = 'UP';
但 isolatedModules(Babel、esbuild、SWC 强制开启)要求每个文件能独立编译,没法跨文件做内联替换。开了 isolatedModules 的项目 const enum 直接报错:
error TS2748: Cannot access 'X' from another module without 'isolatedModules'
or 'preserveConstEnums' flag
TS 5.0 起官方不再推荐 const enum,因为现代构建工具链都默认开 isolatedModules。
首选替代:as const 对象 + 联合类型
const Direction = {
Up: 'UP',
Down: 'DOWN',
Left: 'LEFT',
Right: 'RIGHT',
} as const;
type Direction = typeof Direction[keyof typeof Direction];
// type Direction = 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'
收益:
- 零运行时:
as const对象能完全 tree-shake,没用到的成员直接消失 - 类型安全:
type Direction联合类型保证参数只能 是 4 个字符串之一 - IDE 友好:跳转、重构、查找引用都正常
- 跨工具兼容:Babel、SWC、esbuild 都能处理,不用管
isolatedModules
| 维度 | enum | as const 对象 |
|---|---|---|
| 运行时开销 | 有(数字 enum 是双向对象) | 无(可 tree-shake) |
| 类型保护 | 自动生成联合类型 | 需要手写 typeof |
| isolatedModules | 数字 enum OK,const enum 不行 | 完全兼容 |
| Tree-shaking | 数字 enum 失效 | 完全支持 |
| 反向映射 | 仅数字 enum | 需要手写 helper |
提示:唯一还应该用 enum 的场景是 声明合并(declaration merging) ——enum 允许在不同文件里给同一个 enum 加成员,as const 对象做不到。
References
- TypeScript Handbook: Enums —— 官方手册
- TypeScript 5.0 Release Notes —— const enum 弃用说明
- Should I use enums in TypeScript? - stackoverflow —— 社区共识