You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// 正常的字符初始化:必须把所有初始值都安排上enumResponseMsg{Success='Success',MissingParameter='Missing Parameter',InvalidToken='Invalid Token',UnknownErr='Unknown Error'}// 字符枚举必须初始化全部的值enumResponseMsg{Success='Success',MissingParameter,// Error: Enum member must have initializer.InvalidToken='Invalid Token',UnknownErr='Unknown Error'}// 初始化可以用模版字符串,但是不允许使用变量constfoo='Unknown Error'enumResponseMsg{Success='Success',MissingParameter=`Missing Parameter`,// 可以使用 Template String 形式InvalidToken='Invalid Token',UnknownErr=`${foo}`// Error: Computed values are not permitted in an enum with string valued members.}
enumResponseMsg{Success='Success',MissingParameter='Missing Parameter',InvalidToken='Invalid Token',UnknownErr='Unknown Error'}letmessage=ResponseMsg.MissingParameter;console.log(ResponseMsg[message]);// Error: Property 'Missing Parameter' does not exist on type 'typeof ResponseMsg'
enumResponseStatus{Success=0,MissingParameter=1,InvalidToken=2}functionresponseStatusHandler(status: ResponseStatus): void{// do something...}responseStatusHandler(ResponseStatus.Success);// 符合预期responseStatusHandler(0);// ok???responseStatusHandler(6);// ok???// -------------------------------------------------------------enumResponseMsg{Success='Success',MissingParameter='Missing Parameter',InvalidToken='Invalid Token',UnknownErr='Unknown Error'}functionmsgReporter(msg: ResponseMsg): void{console.log(msg)}msgReporter(ResponseMsg.Success);msgReporter(0);// Argument of type '0' is not assignable to parameter of type 'ResponseMsg'.
constenumDirection{Up,Down,Left,Right,}letdirections=[Direction.Up,Direction.Down,Direction.Left,Direction.Right,];// JS 版本"use strict";letdirections=[0/* Up */,1/* Down */,2/* Left */,3/* Right */,];
深入 Typescript 中的 Enums
前言
枚举(enumaration, aka enum)一直被认为是管理一系列相关联常量,提升代码可读性的一个工具。Typescript 更新的
enum
类型突破了类型层面的限制,在运行时为 Javascript 补充上了这块空白。在 TS 的手册中是这么描述enum
的:但我在看 Effective Typescript 与 Programming Typescript 这两本书的时候,看到两个作者不约而同的对这个类型的部分用法提出了警告,Dan Vanderkam 甚至在 Item 53 建议读者不要使用这个类型并提出了用
|
来取代字符枚举的方法。本文将从基本用法着手,逐步回答两个问题:
基本用法
数字枚举
假设今天我们跟后端同学约定好了一组查询请求的内部响应状态码:
然后一个同学写了如下的响应处理函数:
运行起来没啥毛病,但是下一个维护的同学估计人傻了……
如果我们用了 enum 之后,代码会变成这样子:
Playground
这样代码的可读性是不是直接一个飞跃性进步😎
其中的枚举类型
ResponseStatus
因为枚举值都是数字,被称为数字枚举(Numeric enums)数字枚举初始化
字符枚举
上述的数字枚举提供了很好的代码可读性,但是如果今天需要调试的话,数字枚举提供不了比较有价值的信息
让我们继续上一个例子,只是这次需要把错误原因打印到
console
面板:Playground
可以看到,字符枚举不止增加了代码的可读性,也集中了所有调试信息,提升了代码的可维护性。
字符枚举初始化
混合枚举(不推荐)
混合枚举,顾名思义,就是把数字与字符的枚举混合在一起:
一般而言,除非真的同时需要数字枚举在运行时的特性(稍后解释)以及字符枚举的信息,不然不建议使用混合枚举
混合枚举更像是一种实现上的副产物(后面讲到如何将 enum 转化为 js 代码的时候会讲到)
JS 版本的枚举实现
数字枚举
前面有讲到,Typescript 在运行时层面为 Javascript 填补了枚举这个功能的空白。
但是,V8 解释的还是 Javascript 语言本身,所以 TS 的编译器必须要先把 TS 代码 “翻译” 为等价的 JS 才能够被 V8 进一步解释运行。
让我们回到数字枚举的例子:
Playground
经过编译器的翻译,JS 代码是这样子的:
可以看到,将 enum 翻译为 JS 总共有 3 个步骤:
ResponseStatus
ResponseStatus
枚举项 -> 数字
的映射,再创建了数字 -> 枚举项
的映射这个就是为什么数字枚举支持双向映射的原因。
具体而言,数字枚举支持以下方式调用:
字符枚举
依照惯例,我们还是来看看之前的例子:
Playground
经过编译器的编译,会变成这样:
大致还是相同的,区别在于函数体内:它只创建了
枚举项 -> 字符串
的映射。所以字符枚举是不支持双向映射的。
我们再来看看,当我们不初始化字符枚举的时候会发生什么事:
Playground
可以看到,没有被显式初始化的字符枚举对象被默认给了一个
void 0
所以编译器贴心的给我们报了错
混合枚举
我们来看一个混合枚举的例子:
Playground
可以观察到几点:
undefined
总结
枚举项 -> 字符
的单向映射void 0
,并由 TS 编译器负责报错最佳实践
要了解使用枚举的最佳实践,首先要先知道它有哪些特性与缺陷:
同名 enum 的合并
就像 interface 一样,enum 支持将两个同名的枚举自动合并,考虑到如下例子:
Playground
可以看到,最后一行巧妙的用了一个或运算,避免了
ResponseStatus
被重新初始化,从而可以将同名枚举融合。虽然这个特性对开发者非常友好,但是还是有两个问题的,看如下例子:
Playground
第一个问题是:重复的枚举项会导致报错
这个问题相对不那么严重,因为 TS 在编译的时候就直接给出了明确的报错提示
第二个问题是:如果不给枚举初始值的话,按照之前的编译规则,会自动给新的枚举项从 0 开始的枚举值
这样的后果就是双向映射被覆盖(当然字符枚举就不会有这个问题了)
我们也可以从最后的 console 看到 0 的映射被后来声明的 PermissionDenied 覆盖了
解决这个问题的方法就是:每次都记得给枚举项显式赋值,不要依赖默认赋值
双向映射
数字枚举的双向映射可以让我们轻松做到如下操作:
Playground
反之字符枚举就没办法:
Playground
看起来数字枚举会更灵活一点,但是更灵活真的是好事吗?看如下例子:
Playground
可以看到,上面的
responseStatusHandler
明明想要的是一个枚举类型,但是使用numer
类型的 0 和 6 都成功通过 TS 的校验了(其中 6 甚至还不是枚举内的值)。反观下面的
msgReporter
成功阻止了非枚举类型的传入。综上所述:如果你想用 TS 校验枚举类型,最好使用字符枚举
当然,如果你嫌用枚举麻烦(跨模块使用函数如果函数想要枚举值作为形参则必须手动引入对应枚举),并且愿意牺牲一点代码可读性的话,完全可以使用联合
|
来取代字符枚举:Playground
const enum
虽然枚举为我们的代码提供了不错的可读性,但是它还是产生了不少映射以及 IIFE。在大量使用的时候可能会造成额外的运行时间代价。
于是 const enum 就应运而生了:
Playground
可以看出来,const enum 为我们做的事情很简单:
它不再生成一个对象,也不建立映射关系
它单纯的把枚举值内联到了所有有用到的地方
看起来似乎完美的解决了上述问题,但是真的有这么简单吗?
我们知道,const enum 在编译时会进行处理,而且 TS 独有的特性让我们可以在编译时完成 const enum 的内联,且在运行时导入其他含有同名 const enum 的枚举。这个时候,这两个本应该互相融合的枚举会变成两个不同的值。这个可能导致很多预期之外的 bug。
为了避免上述问题,有两个方案可以参考:
避免使用 const enum,或者设置
preserveConstEnums
为true
。后者会照常产生 IIFE 与对应映射。只在自己能控制的程序中使用它,并且不要把它往外发布或让其他人能够当成库导入。
总结
同名的 enum 会像 interface 一样自动合并,但是重复的枚举项会导致报错。
为了避免同名 enum 自动合并的时候导致双向映射被覆盖,最好对每个枚举值都显式赋值。
数字枚举的双向映射特性可能会给枚举类型的校验带来麻烦,如果想用 TS 校验枚举类型,最好使用字符枚举。
可以使用联合
|
来取代字符枚举。const enum 会通过内联的方式替换枚举值,可以减少普通枚举的开销。
把含有 const enum 的代码库导入的时候可能会导致预期之外的 bug,解决方式是设置编译选项
preserveConstEnums
为true
。如果一定要使用 const enum 的话,只在自己能控制的程序中使用它。
Reference
https://juejin.cn/post/6844904112669065224#heading-3
https://www.php.cn/js-tutorial-479202.html
https://jkchao.github.io/typescript-book-chinese/typings/enums.html#%E6%95%B0%E5%AD%97%E7%B1%BB%E5%9E%8B%E6%9E%9A%E4%B8%BE%E4%B8%8E%E6%95%B0%E5%AD%97%E7%B1%BB%E5%9E%8B
https://medium.com/enjoy-life-enjoy-coding/typescript-%E5%96%84%E7%94%A8-enum-%E6%8F%90%E9%AB%98%E7%A8%8B%E5%BC%8F%E7%9A%84%E5%8F%AF%E8%AE%80%E6%80%A7-%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95-feat-javascript-b20d6bbbfe00
Programming Typescript by Boris Cherny
Effective TypeScript by Dan Vanderkam
The text was updated successfully, but these errors were encountered: