文章目录
前言
前不久,TypeScript 5.8 正式发布,带来了一个让人瞩目的新配置:--erasableSyntaxOnly
。这个配置的核心作用是什么呢?简单来说,它只允许使用“可擦除语法”。如果开启了它,任何不可擦除语法的使用都会直接报错。比如,enum
就是典型的不可擦除语法,一旦在 tsconfig.json
中配置了 "erasableSyntaxOnly": true
,再用 enum 就会直接给你标红。
那么,今天我们就从“可擦除语法”和“不可擦除语法”来聊聊--erasableSyntaxOnly
和enum
枚举
1. 什么是可擦除语法和不可擦除语法
可擦除语法
简单来说,可擦除语法就是那种可以直接去掉的,只在编译的时候存在,不会生成额外运行时代码的语法。像 type
、interface
这些就属于可擦除语法。
例如如下代码, 在编译完成后, 将不会在存在type
、interface
, 即擦除了该语法:
可擦除语法
的作用仅限于为开发者在编写代码时提供类型提示和约束
不可擦除语法
,指的是那些在TypeScript
编译为 JavaScript
时,无法直接被“抹去”,而是会生成额外运行时代码的特性。这类语法不仅存在于编译阶段,还会在最终的 JavaScript 代码中保留,从而影响运行时的行为。典型的例子包括 enum
和带运行时逻辑的 namespace
。
举个例子:
可以看到,enum 和带运行时逻辑的 namespace 在编译后并没有被“擦除”,而是生成了额外的 JavaScript 代码。这些代码会在运行时被解析和执行,增加了运行时的开销。
需要注意的如果namespace
仅用于类型声明, 不包括任何运行时逻辑,那它就是可擦除的,比如:
此时仅包含类型的namespace
就变成可擦除的
因此,不可擦除语法的特点就是:
无法直接被移除
:编译后会生成额外的 JavaScript 代码。影响运行时性能
:会增加运行时的负担,尤其是在大型项目中。
理解了这一点,就能更好地理解 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 enum
是 enum
的一种优化版本,它在编译后会直接将枚举值内联到代码中,避免了生成额外的 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公众号:
程序员付杰
,一起学习前端技能