TypeScript 5.8 让我少踩了一个大坑,顺手再聊聊 --erasableSyntaxOnly

上周五下午,产品突然过来说有个接口的缓存逻辑出了bug,用户看到的链接和实际跳转的不一样。我排查了半天,最后发现是一个三元表达式埋的坑------一边返回了 string,一边返回的其实是 any,结果 any 把类型检查全吞了。正当我准备改代码的时候,同事提醒我:"升一下 TypeScript 到 5.8 试试,这个版本专门修了这种问题。"我半信半疑地升级,然后运行 tsc ------报错信息直接指出了三元表达式里 string 不能赋值给 URL。说实话,这个体验让我有点意外。TypeScript 这波操作让我觉得它终于像个"真正的类型检查器"了,而不是一个会漏报错误的橡皮图章。这篇文章就聊聊 TypeScript 5.8 的几个让我觉得"这版本没白升"的特性。

那个让我少写一堆类型守卫的 conditional return-type narrowing

先说最让我有感的这个改进。 之前写代码的时候,三元表达式里的类型检查一直是让我头疼的事情。看这个例子:

typescript 复制代码
declare const cache: Map<string, any>;

function getUrl(url: string): URL {
    return cache.has(url) ? cache.get(url) : url;
}

我本意是从缓存里拿 URL 对象,如果缓存没有就拿字符串自己造一个。但 cache.get(url) 返回的是 any,而 any | string 会变成 any,所以 TypeScript 之前根本不会报错。这个 bug 就这么埋进去了,在线上跑了三个月才被产品发现。 TypeScript 5.8 之前,这段代码是"合法"的,tsc 不会报任何错误。 TypeScript 5.8 之后,tsc 直接报错:

python 复制代码
error TS2322: Type 'string' is not assignable to type 'URL'.

因为 5.8 会单独检查三元表达式每个分支的返回类型,而不是把它们合成一个 union 之后再来检查。这意味着你写的 cond ? A : B,TypeScript 现在会分别检查 A 能不能赋值给返回值类型、B 能不能赋值给返回值类型。 这个改进在日常写代码的时候特别有用。举个例子,我之前为了避免这种问题,经常会写类型守卫:

javascript 复制代码
function getUrl(url: string): URL {
    const cached = cache.get(url);
    if (cached) {
        if (cached instanceof URL) {
            return cached;
        }
        throw new Error('Invalid cache value');
    }
    return new URL(url);
}

现在有了 conditional return-type narrowing,上面这种防御性代码可以省掉很多。三元表达式直接写,TypeScript 帮你兜底:

php 复制代码
function getUrl(url: string): URL {
    const cached = cache.get(url);
    return cached instanceof URL ? cached : new URL(url);
}

干净多了,而且不会漏掉类型检查。


--erasableSyntaxOnly:给 Node.js 直接跑 TS 铺路

如果说 conditional return-type narrowing 是"查漏补缺",那 --erasableSyntaxOnly 就是 TypeScript 5.8 的"战略级"特性。 Node.js 23.6 开始支持直接运行 TypeScript 文件了,用的是 --experimental-strip-types 参数。但这里有个限制:TS 文件里不能有"有运行时行为的 TS 特有语法"。 什么是有运行时行为的 TS 特有语法?比如:

  • enum 声明
  • namespace(带运行时代码的)
  • 类的参数属性(constructor(public x: number))
  • import = require() 这种 CommonJS 风格的 import
  • export = 这种 CommonJS 风格的 export

如果你在项目里用了这些语法,Node.js 的 --experimental-strip-types 直接报错,不管你是想直接跑还是先 strip 再跑。 TypeScript 5.8 新增了 --erasableSyntaxOnly 标志,就是让你在编译阶段就能发现"这段代码将来没法被 Node.js 直接运行"的问题。

typescript 复制代码
class Point {
    constructor(public x: number, public y: number) { }
}

开启 --erasableSyntaxOnly 之后,tsc 直接报错:

csharp 复制代码
error: This syntax is not allowed when 'erasableSyntaxOnly' is enabled.

我的体感是,这个特性对两种人特别有用:

第一种是想迁移到"Node.js 原生支持 TS"的团队。 如果你正在规划从 ts-node 迁移到 Node.js 内置的 TS 支持,这个 flag 可以帮你提前扫掉那些会在未来报错的代码,不用等到 Node 真的跑不起来再回来改。

第二种是想保持代码"现代化"的开发者。 --erasableSyntaxOnly 本质上是在推动大家用标准 ECMAScript 语法,比如用 const enum 或者对象替代 enum、用 import type 替代 import assertion。这些改动长期来看对代码健康是有好处的。

官方文档建议把 --erasableSyntaxOnly 和 --verbatimModuleSyntax 搭配使用。--verbatimModuleSyntax 会确保 import 语句的格式符合预期,import elision 也不会乱来。这样一套组合拳打下来,你的代码基本上就是"strip-types safe"的了。


--module node18 终于稳定了

之前如果你用的是 Node.js 18,配置 module 选项的时候其实挺尴尬的。 node16 太老,不支持新特性。nodenext 虽然最激进,但行为可能会随着 Node.js 版本变化,不够稳定。你想用 18 的特性但又不想被"未来的 nodenext 行为变化"影响?之前没有好的选择。 TypeScript 5.8 推出了稳定的 --module node18 选项。现在你可以在 tsconfig.json 里直接写:

json 复制代码
{
    "compilerOptions": {
        "module": "node18"
    }
}

这个选项有几个明确的边界:

  • 不支持从 CJS require() ESM 模块(这个只在 nodenext 里支持)
  • 允许 import assertions(但 nodenext 已经废弃了,改用 import attributes)

简单说,--module node18 就是给"不想折腾,想稳定用 Node 18"的人准备的。如果你在用 Node.js 18,这应该是目前最稳妥的 module 配置。


--libReplacement false:小改动,但省心

这个改动比较低调,但我挺喜欢的。 TypeScript 4.5 引入了 lib 文件替换功能,可以把默认的 lib.d.ts 换成自定义版本。比如你可以通过 @typescript/lib-dom 来指定使用某个版本的 DOM 类型定义。 但问题是,即使你根本没用这个功能,TypeScript 也会每次编译的时候去检查 node_modules 里有没有这个包存在。 TypeScript 5.8 引入了 --libReplacement 选项。如果你没在用 lib 替换功能,可以显式关闭它:

json 复制代码
{
    "compilerOptions": {
        "libReplacement": false
    }
}

这样 TypeScript 就不会每次都去做那个没意义的检查了。官方文档说,未来 --libReplacement false 可能会成为默认行为,所以如果你现在依赖这个行为,记得显式开启。


写在最后

说实话,之前每次 TypeScript 发布新版本,我都是先看 changelog 有没有 breaking changes,遇到新特性都是"观望观望"。TypeScript 5.8 算是让我稍微转变了一下态度。 conditional return-type narrowing 这个特性,解决了三元表达式类型检查这个"看起来是小问题、其实容易埋大坑"的场景。TypeScript 这次是真正在补齐类型系统的短板。 --erasableSyntaxOnly 则是一个面向未来的特性。如果你和我一样正在关注"Node.js 原生跑 TS"这个方向,这个 flag 可以让你提前做好准备,不用等到 Node 真的推了再手忙脚乱地改代码。

升级成本不高,收益是实打实的。如果你还没升级,建议找个低峰期跑一下 npm install typescript@5.8,看看 tsc 会不会给你一些惊喜。

相关推荐
福兮说3 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
AI情绪识别开源3 小时前
HFpEF 心房-心室耦联效率无创监测方向(AVCEI)
typescript·laravel·perl
郑州光合科技余经理3 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
小呆呆6664 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
凤城老人4 小时前
从 PyQt6 到 Electron:给 Edge TTS 做一个“多角色配音机“的踩坑手记
javascript·typescript·electron
Dovis(誓平步青云)4 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
码艺-Alimjan5 小时前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
可乐鸡翅yeah_5 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线
IT_陈寒6 小时前
Vite静态资源导入这个坑我帮你们踩过了
前端·人工智能·后端
广州华水科技6 小时前
大坝安全监测解决方案:单北斗GNSS形变监测系统应用与维护
前端