停止盲目地将 `enum` 替换为 `as const`
Source: Dev.to
Introduction
你可能已经看到很多文章建议我们用 as const 对象来替代 TypeScript 的 enum,理由包括:
- 更好的 tree‑shaking
- 更小的 bundle 大小
- 更少的运行时代码
这些建议并非完全错误,但往往过于简化。
真正的问题不是“我们应该停止使用 enum 吗?”而是:我们是否了解 TypeScript 在幕后生成了什么代码,以及我们在做哪些权衡?
Numeric enums and the generated code
对于数字枚举,TypeScript 会生成额外的运行时代码。
enum Direction {
Up,
Down,
Left,
Right,
}上述代码会被大致转译为下面的 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 = {}));乍一看这很奇怪。TypeScript 创建了一个 IIFE(立即调用函数表达式),并把 Direction["Up"] = 0 赋值。该赋值返回 0,于是 TypeScript 还会设置 Direction[0] = "Up"。
运行时我们得到的对象大致是:
{
Up: 0,
Down: 1,
Left: 2,
Right: 3,
0: "Up",
1: "Down",
2: "Left",
3: "Right"
}这称为 反向映射:
Direction.Up // 0
Direction[0] // "Up"as const objects
现在把它和普通的 as const 对象进行比较:
const DirectionConst = {
Up: "Up",
Down: "Down",
Left: "Left",
Right: "Right",
} as const;这只是一个普通的 JavaScript 对象,没有反向映射,也没有额外的运行时代码。因此,许多开发者发现 as const 在以下场景下非常有用:
- 前端应用
- 配置对象
- 路由名称
- 状态值
- 动作类型
- 其他以 JS 为主的模式
Enums aren’t “bad”
问题不在于枚举本身不好,而是很多人盲目地用 as const 替代它们,而没有考虑具体的使用场景。并非所有枚举都一样:
- 数字枚举 会生成反向映射。
- 字符串枚举 不会 生成反向映射。
const enum有一套不同的权衡(它在编译时内联)。
as const 并不是每种枚举使用场景的 1:1 替代品。在替换之前,先问自己:
- 我们需要反向映射吗?
- 我们需要运行时的枚举对象吗?
- 我们使用的是数字枚举还是字符串枚举?
- 这是应用代码、库代码,还是协议/编译器风格的代码?
- 我们是为了明确性、体积大小,还是 JavaScript 简洁性在优化?
Conclusion
as const 是一种很好的模式,枚举也并非天生错误。所谓的“高级做法”不是盲目跟随口号如“停止使用枚举”,而是要理解 TypeScript 的内部工作原理,并为具体的使用场景选择合适的工具。