对比 enum 和 as const

109 阅读3分钟

对比 enumas const 的优缺点

enum

  • 优点

    1. 传统的枚举模式,对来自其他语言的开发者更熟悉
    2. 数字枚举支持自增和双向查找
    3. 明确的命名空间,避免命名冲突
  • 缺点

    1. 运行时侵入性,增加运行时开销

      • TypeScript 的 enum 在编译后会生成额外的 JavaScript 双向映射数据,这会增加运行时的开销。
    2. 不可继承扩展属性

      • 新增一个属性时,必须要重新要重新写一个 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,
      }
      
    3. 类型问题

      // 推导的类型是 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

  • 优点

    1. 可继承扩展
    2. 类型推导
    3. 灵活结果,支持任意嵌套对象或数组,定义更复杂的常量结构
    4. 运行时开销小
  • 缺点

    1. 类型推导,获取到的类型还需要自己来推导出为 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
      
    2. 不直接支持自增枚举值

    3. 需要额外类型操作来模拟完整枚举功能

      /**
       * 将联合类型转换为交叉类型 (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]>;
      
    4. 运行时对象仍可通过 JavaScript 方式修改(仅类型检查阻止)

可擦除语法

  • 可擦除语法就是可以直接去掉的、仅在编译时存在、不会生成额外运行时代码的语法,例如 type,interface。

不可擦除语法

  • 不可擦除语法就是不能直接去掉的、需要编译为 JS 且会生成额外运行时代码的语法,例如 enum,namesapce(with runtime code)。」

选择建议

  1. ​​优先考虑 as const 的情况​​:
  • 对代码体积和性能敏感的项目
  • 需要深度不可变数据结构
  • 需要精确的字面量类型
  • 项目强调纯类型安全
  • 与现有 JavaScript 代码库高度集成
  1. ​考虑使用 enum 的情况​​:
  • 需要数字枚举的自增或双向映射特性
  • 进行位运算操作
  • 已有大量基于 enum 的代码库
  • 团队对 enum 模式更熟悉

参考文档

Typescript 你必须这么用 ❌Enum,✅Union

别再用 TypeScript enum 啦!前端枚举的现代打开方式

TypeScript 官方宣布弃用 Enum?Enum 何罪之有?