告别 enum?探索 TypeScript 中的可擦除语法与替代方案

前言

前不久,TypeScript 5.8 正式发布,带来了一个让人瞩目的新配置:--erasableSyntaxOnly。这个配置的核心作用是什么呢?简单来说,它只允许使用“可擦除语法”。如果开启了它,任何不可擦除语法的使用都会直接报错。比如,enum 就是典型的不可擦除语法,一旦在 tsconfig.json 中配置了 "erasableSyntaxOnly": true,再用 enum 就会直接给你标红。

那么,今天我们就从“可擦除语法”和“不可擦除语法”来聊聊--erasableSyntaxOnlyenum枚举



1. 什么是可擦除语法和不可擦除语法

可擦除语法简单来说,可擦除语法就是那种可以直接去掉的,只在编译的时候存在,不会生成额外运行时代码的语法。像 typeinterface 这些就属于可擦除语法。

例如如下代码, 在编译完成后, 将不会在存在typeinterface , 即擦除了该语法:

可擦除语法的作用仅限于为开发者在编写代码时提供类型提示和约束

不可擦除语法,指的是那些在TypeScript 编译为 JavaScript 时,无法直接被“抹去”,而是会生成额外运行时代码的特性。这类语法不仅存在于编译阶段,还会在最终的 JavaScript 代码中保留,从而影响运行时的行为。典型的例子包括 enum 和带运行时逻辑的 namespace

举个例子:

可以看到,enum 和带运行时逻辑的 namespace 在编译后并没有被“擦除”,而是生成了额外的 JavaScript 代码。这些代码会在运行时被解析和执行,增加了运行时的开销。

需要注意的如果namespace仅用于类型声明, 不包括任何运行时逻辑,那它就是可擦除的,比如:

此时仅包含类型的namespace 就变成可擦除的

因此,不可擦除语法的特点就是:

  1. 无法直接被移除:编译后会生成额外的 JavaScript 代码。
  2. 影响运行时性能:会增加运行时的负担,尤其是在大型项目中。

理解了这一点,就能更好地理解 TypeScript 5.8 引入 --erasableSyntaxOnly 配置的初衷:通过限制不可擦除语法的使用,优化代码的运行时性能和体积。


2. TypeScript 为什么要出 --erasableSyntaxOnly

在官网对于新增的--erasableSyntaxOnly配置进行了说明:

大概的意思是这个配置的诞生是为了配合 Node.js 的一项全新特性。

从 Node.js 23.6.0 开始,Node.js 默认支持直接执行仅包含可擦除语法的 TypeScript 文件。这一功能的核心在于,Node.js 能够在运行时“忽略”掉 type、interface 等可擦除语法,从而实现无需编译、直接执行 TypeScript 文件的能力。为了让 TypeScript 更好地与 Node.js 的这一特性兼容,官方才引入了 --erasableSyntaxOnly 配置。

换句话说,--erasableSyntaxOnly 的出现,并不是为了淘汰 enum 或其他不可擦除语法,而是为了支持 Node.js 的新特性。它的目的是帮助开发者更高效地在 Node.js 中直接运行 TypeScript 代码,同时确保代码的运行时性能和体积达到最佳状态。

举个例子:

如果你在 Node.js 23.6.0 及以上版本中运行如下 TypeScript 文件:

Node.js 会直接执行这段代码,并输出结果:

但如果使用了 enum 或其他不可擦除语法:

Node.js 将无法直接执行,因为 enum 会生成额外的运行时代码。此时,--erasableSyntaxOnly 的作用就体现出来了:它会提醒开发者避免使用不可擦除语法,确保代码能够在 Node.js 的新特性下顺利运行。

因此,--erasableSyntaxOnly 的引入,本质上是 TypeScript 与 Node.js 生态的一次深度协同,而不是对某些语法的否定。它体现了 TypeScript 对技术发展趋势的敏锐洞察,以及对开发者体验的持续优化。



3. Enum 的核心问题

尽管 enum 是 开发中常用的语法,用来定义枚举值。但其本身也存在一些值得注意的问题:

3.1 枚举默认值

常用enum 的都知道, enum 没有设置默认值时, 默认值从0开始, 乍一看这没什么问题,但它有个隐藏的“坑”:你可以直接传一个数字给它,而 TypeScript 居然不会报错! 这就打开了“潘多拉魔盒”,可能会导致一些意想不到的类型安全问题。

这里 handleStatus 函数本来只接受 Status 枚举值,但因为 enum 的数字特性,你甚至可以传枚举值(比如 2)进去,而 TypeScript 居然不会给出任何警告!


3.2 不支持字符串枚举值

这里还有一种场景,可能会让你更困惑:有时候,你既希望可以传入枚举类型,又希望可以传入枚举值的字面量。比如这样:

看,这里的问题就来了:你定义了一个枚举类型,但当你试图直接传入枚举值的字面量(比如 ‘add’)时,TypeScript 会直接告诉你:“不行,你得传 Method.ADD!” 这似乎很合理,毕竟类型系统就是用来约束行为的嘛。


但问题在于,这跟之前的“数字枚举”场景形成了鲜明的对比!

还记得吗?在数字枚举里,你可以随便传一个数字,TypeScript 都不会拦你。比如:

这不就是妥妥的“双标”吗?

对数字枚举来说,传个数字是合法的;对字符串枚举来说,传个字符串字面量反而是非法的!这种不一致的行为会让人很抓狂:凭什么数字可以“乱来”,字符串却要“守规矩”?


3.3 增加运行时开销

enum 是不可擦除语法,编译后会生成额外的 JavaScript 代码。这种代码会增加运行时开销,尤其是在复杂的项目中,可能会导致性能问题。

这种额外的代码会让项目打包后的文件变大,尤其是在对性能敏感的场景中,可能会成为负担。



4. Enum 的替代方案

既然 enum 有这么多“坑”,那有没有更好的替代方案?答案是肯定的! 事实上,TypeScript 提供了多种更灵活、更轻量的方案来处理常量或枚举的场景。下面给大家介绍几种常见的替代方案,帮助你在目中更好地规避 enum 的缺陷。


4.1 const enum

const enumenum 的一种优化版本,它在编译后会直接将枚举值内联到代码中,避免了生成额外的 JavaScript 代码

可以看到,Status.Pending 直接被替换成 0,没有生成任何额外的运行时对象。这种“内联优化”使得 const enum 特别适合对性能敏感的场景。

不过,const enum 也存在一些问题, 比如跨模块引用问题。


4.2 联合类型(Union Types)

联合类型是最轻量级的替代方案,特别适用于固定值的场景。它的优势在于:

  • 类型安全性高:只能传入定义的值。
  • 零运行时开销:编译后没有额外代码。
  • 语法简洁:用起来非常直观。

联合类型是一个非常简单的替代方案。它可以很好地限制传入的值,但没有一个地方可以统一维护枚举值。


4.3 类型字面量 + as const(推荐)

常通过定义一个常量对象,并在联合类型中引用这个对象的值,既能实现枚举值的集中管理,又能享受联合类型的类型安全性。

4.4 自定义Class 类实现

我们也可以自定义Class 实现枚举逻辑。这也是没有Typescript时 常用的处理枚举的方法。

示例



总结

enum 有没有被抛弃?其实并没有。–erasableSyntaxOnly 的出现是为了配合 Node.js 的新特性,而不是为了淘汰 enum。不过,enum 确实存在一些问题,比如类型不安全、不支持字面量、增加运行时开销等。因此,如果你的项目对性能要求比较高,或者需要更严格的类型安全,不妨考虑使用 const enum、联合类型或者类型字面量 + as const 来替代 enum。

最后,你觉得 enum 还有存在的必要吗?欢迎在评论区分享你的看法!

如果觉得本文对你有帮助,希望能够给我点赞支持一下哦 💪 也可以关注wx公众号:程序员付杰 ,一起学习前端技能

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员付杰

欢迎投喂, 一起共享知识的盛宴

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值