• TypeScript —— 枚举类型 enum 的红与黑


    TypeScript 设计的初衷是 JavaScript + Types,所有 TypeScript 的特性不改变运行时的行为

    反过来说,如果在 TS 代码中去掉静态类型,应该得到一份完整有效的 JS 代码

    这样的好处在于,我们可以通过 ESbuild 而不是 tsc 完成我们的 TS 代码到 JS 代码的转换

    但实际上 TypeScript 中有一个特殊类型破坏了这种构想,它就是 Enum

     

     

    一、什么是 Enum

    在 TypeScript 中可以通过 enum 来定义一组常量,并将这些常量放到同一个对象中管理:

    enum Language {
      ZH_CN = 'zh_CN',
      ZH_HK = 'zh_HK',
      ZH_TW = 'zh_TW',
      EN_US = 'en_US',
      EN_GB = 'en_GB',
    }

    和 type、interface 类似,enum 可以直接作为静态类型使用

    function getLocals(lang: Language) {
      return `hello ${lang}`;
    }

    但在调用这个函数的时候,传入的参数不能是 enum 的值,而应该是 enum 的引用

     

    从这里就会发现 enum 的特性:可以当做对象使用

    摘一段官方文档的描述:枚举类型在运行时会被编译为一个对象,包含正向映射(name -> value),如果是数值枚举,还会生成反向映射(value -> name)

    其实不只是运行时,普通的枚举类型最终都会编译为对象

    // 编译前
    enum Enum {
      A = 1,
      B = 2,
    }
    
    // 编译后
    var Enum;
    (function (Enum) {
      // 因为是数值枚举,所以还生成了反向映射
      Enum[Enum["A"] = 1] = "A";
      Enum[Enum["B"] = 2] = "A";
    })(Enum || (Enum = {}));

    这时可以考虑使用 const enum 来优化编译结果,它不会编译未使用的枚举项,而且不会生成对象,在编译后只会保留枚举值

    // 编译前
    enum Enum {
      A = 1,
      B = 2,
    }
    const arr = [Enum.A]
    
    // 编译后
    var arr = [1 /* A */];

     

     

    二、Enum 的优缺点

    由于 enum 可以当做对象使用,所以在管理常量上非常方便

    比如上面的 Language,如果需要将 'zh_CN' 改为 'zh_cn',最终只要调整一下 Language 中 ZH_CN 的值就行,因为在使用的时候都是用的 Language.ZH_CN

     

    除此之外,如果某个数据结构需要用到字符串和数字的双向映射,这时候用 enum 会简单很多,因为数值枚举会生成正向和反向映射

    enum Options {
      apple = 1,
      pear = 2,
      lemon = 3,
      orange = 4,
    }
    
    console.log(Options[1]); // apple

     


     

    而 enum 的缺点,就是在一开始提到的:违背了 TypeScript = JavaScript + Types 的构想

    比如下面的这段 TS 代码:

    type DataItem = {
      label: string;
      value: number | string;
    };
    
    function formatLabels(arr: DataItem[]) {
      return Array.isArray(arr) ? arr.map((x) => x.label).join(', ') : '';
    }
    
    const data: DataItem[] = [
      { label: 'wise', value: 1 },
      { label: 'wrong', value: 2 },
    ];
    
    formatLabels(data);

    如果把 DataItem 删掉,这段代码就变成了完整的 JS 代码

     

    而下面这段使用 enum 的代码

    enum Language {
      ZH_CN = 'zh_CN',
      ZH_HK = 'zh_HK',
      ZH_TW = 'zh_TW',
      EN_US = 'en_US',
      EN_GB = 'en_GB',
    }
    
    function getLocals(lang: Language) {
      return `hello ${lang}`;
    }
    
    getLocals(Language.ZH_CN);

    由于 enum 可以当做对象使用,所以如果删掉 Language,这段代码就无法运行

    而且在作为静态类型使用的时候,enum 还会带来额外的心智负担

    上面的 Language 如果换成联合类型的写法,可能更符合直觉:

    type Language = 'zh_CN' | 'zh_HK' | 'zh_TW' | 'en_US' | 'en_GB';

    最后,也是最大的缺点:由于使用了 enum,我们不得不使用 tsc 而非 ESbuild 来编译项目,导致整个编译过程的开销巨大

     

     

    三、可选的替代方案

    如果很在意编译过程的优化,可以考虑下面的替代方案

     

    1. union type

    type Language = 'zh_CN' | 'zh_HK' | 'zh_TW' | 'en_US' | 'en_GB';
    
    function getLocals(lang: Language) {
      return `hello ${lang}`;
    }
    
    getLocals('zh_CN');

    这个方案简单粗暴,抛弃 enum 的特性,使用联合类型来代替枚举

    其优点是通俗易懂,而且删掉类型后就是一段正常的 JS 代码

    但缺点也很明显,不容易维护。假如需要将 'zh_CN' 改为 'zh_cn',那么所有用到了 'zh_CN' 的地方都要调整

     

    2. object as const

    const LangConstant = {
      ZH_CN: 'zh_CN',
      ZH_HK: 'zh_HK',
      ZH_TW: 'zh_TW',
      EN_US: 'en_US',
      EN_GB: 'en_GB',
    } as const;
    
    type ValueOf<T> = T[keyof T];
    
    type Language = ValueOf<typeof LangConstant>;
    // "zh_CN" | "zh_HK" | "zh_TW" | "en_US" | "en_GB"
    
    function getLocals(lang: Language) {
      return `hello ${lang}`;
    }
    
    getLocals(LangConstant.ZH_CN);
    
    getLocals('zh_CN'); // Language 是一个联合类型,所以这里并不会报错,但不推荐

    直接创建一个 JS 对象来维护常量,这样就解决了方案一不易维护的问题

    然后通过 keyof 和 typeof 获取到对象的值,并形成联合类型

    这段代码删掉静态类型依然能够正常运行。除了实现上稍微有点复杂以外,是一个很不错的方案

     


    不管是 union type 还是 object as const,其实都是对 enum 的吹毛求疵

    如果项目不追求极致的编译优化,大可以放心使用 enum;如果不需要反向映射,使用 const enum 或许是一个最优解

     

     

    P.S. 关于 enum 的小技巧

    1. 获取枚举的 key 类型

    type LangKeys = keyof typeof Language;

     

    2. 获取枚举的 value 类型

    type LangValues = `${Language}`;

     

  • 相关阅读:
    Android开发探秘之一:创建可以点击的Button
    TCP之心跳包实现思路
    Asp.net与Dojo交互:仪器仪表实现
    Asp.net Json数据解析的一种思路
    Asp.net通过Jquery操作WebService进行Ajax读写
    GridView自定义删除操作
    从客户端中检测到有潜在危险的request.form值
    JUC-Condition线程通信
    ModelAndView 配置与使用
    SpringMVC之ModelAndView的用法(转)
  • 原文地址:https://www.cnblogs.com/wisewrong/p/16374104.html
Copyright © 2020-2023  润新知