对比 enum 和 as const 的优缺点
enum
-
优点
- 传统的枚举模式,对来自其他语言的开发者更熟悉
- 数字枚举支持自增和双向查找
- 明确的命名空间,避免命名冲突
-
缺点
-
运行时侵入性,增加运行时开销
- TypeScript 的 enum 在编译后会生成额外的 JavaScript 双向映射数据,这会增加运行时的开销。
-
不可继承扩展属性
- 新增一个属性时,必须要重新要重新写一个 enum,无法继承
enum DirectionNumber { /** 上:1 */ Up = 1, /** 下:2 */ Down = 2, } enum DirectionString { /** 上:up */ Up = 'Up', /** * 下:down */ Down = 'Down', } enum NewDirectionNumber { /** 上:1 */ Up = 1, /** 下:2 */ Down = 2, /** 左:3 */ Left = 3, /** 右 */ Right = 4, } -
类型问题
// 推导的类型是 DirectionNumber type DirectionNumberValue = (typeof DirectionNumber)[keyof typeof DirectionNumber]; // 如果使用 type DirectionNumberValue = `${DirectionNumber}` 又会将数字推导为字符串 // 推导的类型是 DirectionString type DirectionStringValue = (typeof DirectionString)[keyof typeof DirectionString]; function test1(t: DirectionNumberValue) {} function test2(t: DirectionStringValue) {} test1(1); // ok test1(DirectionNumber.Up); // ok test2('up'); //报错 test2(DirectionString.Up); // ok
-
as const
-
优点
- 可继承扩展
- 类型推导
- 灵活结果,支持任意嵌套对象或数组,定义更复杂的常量结构
- 运行时开销小
-
缺点
-
类型推导,获取到的类型还需要自己来推导出为 Tuple 类型
const Direction = { /** 上:1 */ Up: 1, /** 下:2 */ Down: 2, } as const; type DirectionKey = keyof typeof Direction; // "Up" | "Down" type DirectionValue = (typeof Direction)[DirectionKey]; // 1 | 2 const DirectionKeys = Object.keys(Direction) as DirectionKey[]; // ("Up" | "Down")[] const DirectionValues = Object.values(Direction); // (1 | 2)[] const len = DirectionKeys.length; // number -
不直接支持自增枚举值
-
需要额外类型操作来模拟完整枚举功能
/** * 将联合类型转换为交叉类型 (Convert union type to intersection type) * @template U - 联合类型 (Union type to be converted) */ export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ( k: infer I ) => void ? I : never; /** * 从联合类型中提取最后一个类型 (Extracts the last type from a union type) * @template U - 联合类型 (Union type to process) */ export type LastInUnion<U extends any> = UnionToIntersection<U extends U ? (x: U) => 0 : never> extends (x: infer L) => 0 ? L : never; /** * 将联合类型转换为元组类型 (Convert union type to tuple type) * @template U - 联合类型 (Union type to be converted) * @template Last - 联合类型中的最后一个类型 (Last type in the union) */ export type UnionToTuple<U extends any, Res extends any[] = [], Last = LastInUnion<U>> = [ U, ] extends [never] ? Res : UnionToTuple<Exclude<U, Last>, [Last, ...Res]>; -
运行时对象仍可通过 JavaScript 方式修改(仅类型检查阻止)
-
可擦除语法
- 可擦除语法就是可以直接去掉的、仅在编译时存在、不会生成额外运行时代码的语法,例如 type,interface。
不可擦除语法
- 不可擦除语法就是不能直接去掉的、需要编译为 JS 且会生成额外运行时代码的语法,例如 enum,namesapce(with runtime code)。」
选择建议
- 优先考虑 as const 的情况:
- 对代码体积和性能敏感的项目
- 需要深度不可变数据结构
- 需要精确的字面量类型
- 项目强调纯类型安全
- 与现有 JavaScript 代码库高度集成
- 考虑使用 enum 的情况:
- 需要数字枚举的自增或双向映射特性
- 进行位运算操作
- 已有大量基于 enum 的代码库
- 团队对 enum 模式更熟悉
参考文档
Typescript 你必须这么用 ❌Enum,✅Union