本篇依然来自于我们的 《前端周刊》 项目!
由团队成员 0bipinnata0 翻译,这位佬有技术追求、翻译风格精准细腻,还擅长挖掘原文背后的技术细节~
欢迎大家 进群 针对TypeScript 新版本同该佬深度交流😁 以及持续追踪全球最新前端资讯!!

正文如下:
今天我们很兴奋地宣布 TypeScript 5.9 的发布!
如果你对 TypeScript 不熟悉,它是一种在 JavaScript 基础上通过添加类型语法来构建的语言。通过类型,TypeScript 可以提前检查你的代码以避免错误。TypeScript 类型检查器完成了所有这些工作,同时也是你的编辑器和其他地方出色工具的基础,让编码变得更加容易。如果你在 Visual Studio 和 VS Code 等编辑器中编写过 JavaScript,TypeScript 甚至为你可能已经在使用的功能(如自动完成、跳转到定义等)提供支持。你可以在我们的网站上了解更多关于 TypeScript 的信息。
但如果你已经熟悉了,你可以今天就开始使用 TypeScript 5.9!
Shell
npm install -D typescript
让我们来看看 TypeScript 5.9 的新功能!
自 Beta 和 RC 版本以来的新变化?
自发布候选版本以来,TypeScript 5.9 没有任何变化。
自 5.9 beta版本以来,我们修复了一些报告的问题,包括将 AbortSignal.abort() 恢复到 DOM 库中。此外,我们还添加了关于显著行为变化的章节。
精简和更新的 tsc --init
一段时间以来,TypeScript 编译器一直支持 --init
标志,可以在当前目录中创建 tsconfig.json
。在过去几年中,运行 tsc --init
会创建一个非常"完整"的 tsconfig.json
,其中包含注释掉的设置及其描述。我们这样设计的目的是让选项易于发现和切换。
然而,根据外部反馈(以及我们自己的经验),我们发现人们通常会立即删除这些新 tsconfig.json
文件的大部分内容。当用户想要发现新选项时,我们发现他们依赖编辑器的自动完成功能,或者导航到我们网站上的 tsconfig 参考(生成的 tsconfig.json
会链接到那里!)。每个设置的作用也在同一页面上有文档说明,并且可以通过编辑器悬停/工具提示/快速信息看到。虽然显示一些注释掉的设置可能有帮助,但生成的 tsconfig.json
通常被认为是过度的。
我们还认为是时候让 tsc --init
初始化时使用比我们已经启用的更多规范性设置了。我们研究了用户在创建新 TypeScript 项目时遇到的一些常见痛点和小问题。例如,大多数用户编写模块(而不是全局脚本),--moduleDetection
可以强制 TypeScript 将每个实现文件视为模块。开发者通常也希望在其运行时中直接使用最新的 ECMAScript 功能,因此 --target
通常可以设置为 esnext。JSX
用户经常发现回去设置 --jsx
是不必要的摩擦,而且其选项略显混乱。通常,项目最终会从 node_modules/@types
加载比 TypeScript 实际需要的更多声明文件;但指定一个空的 types
数组可以帮助限制这一点。
在 TypeScript 5.9 中,不带其他标志的纯 tsc --init
将生成以下 tsconfig.json
:
JSON
{
// Visit https://aka.ms/tsconfig to read more about this file
"compilerOptions": {
// File Layout
// "rootDir": "./src",
// "outDir": "./dist",
// Environment Settings
// See also https://aka.ms/tsconfig_modules
"module": "nodenext",
"target": "esnext",
"types": [],
// For nodejs:
// "lib": ["esnext"],
// "types": ["node"],
// and npm install -D @types/node
// Other Outputs
"sourceMap": true,
"declaration": true,
"declarationMap": true,
// Stricter Typechecking Options
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
// Style Options
// "noImplicitReturns": true,
// "noImplicitOverride": true,
// "noUnusedLocals": true,
// "noUnusedParameters": true,
// "noFallthroughCasesInSwitch": true,
// "noPropertyAccessFromIndexSignature": true,
// Recommended Options
"strict": true,
"jsx": "react-jsx",
"verbatimModuleSyntax": true,
"isolatedModules": true,
"noUncheckedSideEffectImports": true,
"moduleDetection": "force",
"skipLibCheck": true,
}
}
支持 import defer
TypeScript 5.9 引入了对 ECMAScript 延迟模块评估提案的支持,使用新的 import defer
语法。此功能允许你导入模块而不立即执行模块及其依赖项,从而更好地控制何时发生工作和副作用。
该语法只允许命名空间导入:
TypeScript
import defer * as feature from "./some-feature.js";
import defer
的主要好处是模块只有在首次访问其导出之一时才会被评估。考虑这个例子:
TypeScript
// ./some-feature.ts
initializationWithSideEffects();
function initializationWithSideEffects() {
// ...
specialConstant = 42;
console.log("Side effects have occurred!");
}
export let specialConstant: number;
当使用 import defer
时,initializationWithSideEffects()
函数不会被调用,直到你实际访问导入命名空间的属性:
TypeScript
import defer * as feature from "./some-feature.js";
// 还没有发生副作用
// ...
// 一旦访问 `specialConstant`,`feature` 模块的内容就会运行,副作用就会发生。
console.log(feature.specialConstant); // 42
由于模块的评估被延迟到你访问模块的成员时,你不能在 import defer
中使用命名导入或默认导入:
TypeScript
// ❌ 不允许
import defer { doSomething } from "some-module";
// ❌ 不允许
import defer defaultExport from "some-module";
// ✅ 只支持这种语法
import defer * as feature from "some-module";
请注意,当你编写 import defer
时,模块及其依赖项已完全加载并准备好执行。这意味着模块需要存在,并且将从文件系统或网络资源加载。常规 import
和 import defer
之间的关键区别在于,语句和声明的执行被延迟到你访问导入命名空间的属性时。
此功能对于有条件地加载具有昂贵或平台特定初始化的模块特别有用。它还可以通过将应用功能的模块评估延迟到实际需要时来提高启动性能。
请注意,import defer
不会被 TypeScript 转换或"降级"。它旨在用于原生支持该功能的运行时,或者由可以应用适当转换的工具(如打包器)使用。这意味着 import defer
只能在 --module
模式 preserve
和 esnext
下工作。
我们要感谢 Nicolò Ribaudo,他在 TC39 中倡导了这个提案,并为此功能提供了实现。
支持 --module node20
TypeScript 为 --module
和 --moduleResolution
设置提供了几个 node*
选项。最近,--module nodenext
支持从 CommonJS 模块中 require()
ECMAScript 模块的能力,并正确拒绝导入断言(支持符合标准的导入属性)。
TypeScript 5.9 为这些设置带来了一个稳定的选项,称为 node20
,旨在模拟 Node.js v20 的行为。与 --module nodenext
或 --moduleResolution nodenext
不同,此选项在未来不太可能有新的行为。同样与 nodenext
不同,指定 --module node20
将暗示 --target es2023
,除非另有配置。另一方面,--module nodenext
暗示浮动的 --target esnext
。
有关更多信息,请查看此处的实现。
DOM API 中的摘要描述
以前,TypeScript 中的许多 DOM API 只链接到 API 的 MDN 文档。这些链接很有用,但它们没有提供 API 功能的快速摘要。感谢 Adam Naji 的一些更改,TypeScript 现在基于 MDN 文档为许多 DOM API 包含摘要描述。你可以在这里和这里看到更多这些更改。
可展开的悬停提示(预览版)
快速信息(也称为"编辑器工具提示"和"悬停")对于查看变量的类型或类型别名的实际引用非常有用。不过,人们通常希望更深入地了解快速信息工具提示中显示的内容的详细信息。例如,如果我们将鼠标悬停在以下示例中的参数 options
上:
TypeScript
export function drawButton(options: Options): void
我们得到的是 (parameter) options: Options
。

参数声明的工具提示只显示 。
我们真的需要跳转到类型 Options
的定义才能看到这个值有哪些成员吗?
以前,确实是这样的。为了解决这个问题,TypeScript 5.9 现在预览了一个称为可展开悬停或"快速信息详细程度"的功能。如果你使用像 VS Code 这样的编辑器,你现在会在这些悬停工具提示的左侧看到 +
和 -
按钮。点击 +
按钮将更深入地展开类型,而点击 -
按钮将折叠到上一个视图。

此功能目前处于预览阶段,我们正在寻求 TypeScript 和我们在 Visual Studio Code 上的合作伙伴的反馈。有关更多详细信息,请参阅此处此功能的 PR。
可配置的最大悬停长度
有时,快速信息工具提示可能会变得很长,以至于 TypeScript 会截断它们以使其更易读。这里的缺点是,通常最重要的信息会从悬停工具提示中省略,这可能令人沮丧。为了解决这个问题,TypeScript 5.9 的语言服务器支持可配置的悬停长度,可以在 VS Code 中通过 js/ts.hover.maximumLength
设置进行配置。
此外,新的默认悬停长度比以前的默认值大得多。这意味着在 TypeScript 5.9 中,默认情况下你应该在悬停工具提示中看到更多信息。有关更多详细信息,请参阅此处此功能的 PR 和此处对 Visual Studio Code 的相应更改。
优化
在映射器上缓存实例化
当 TypeScript 用特定类型参数替换类型参数时,它可能会一遍又一遍地实例化许多相同的中间类型。在像 Zod 和 tRPC 这样的复杂库中,这可能导致性能问题和关于过度类型实例化深度的错误报告。感谢 Mateusz Burzyński 的更改,TypeScript 5.9 能够在特定类型实例化的工作已经开始时缓存许多中间实例化。这反过来避免了大量不必要的工作和分配。
避免在 fileOrDirectoryExistsUsingSource 中创建闭包
在 JavaScript 中,函数表达式通常会分配一个新的函数对象,即使包装函数只是将参数传递给另一个没有捕获变量的函数。在文件存在性检查的代码路径中,Vincent Bailly 发现了这些传递函数调用的示例,尽管底层函数只接受单个参数。考虑到在大型项目中可能发生的存在性检查数量,他引用了大约 11% 的速度提升。在此处查看有关此更改的更多信息。
显著的行为变化
lib.d.ts 变化
为 DOM 生成的类型可能会影响代码库的类型检查。
此外,一个显著的变化是 ArrayBuffer
已被更改,使其不再是几种不同 TypedArray
类型的超类型。这也包括 UInt8Array
的子类型,例如来自 Node.js 的 Buffer
。因此,你会看到新的错误消息,例如:
Shell
error TS2345: Argument of type 'ArrayBufferLike' is not assignable to parameter of type 'BufferSource'.
error TS2322: Type 'ArrayBufferLike' is not assignable to type 'ArrayBuffer'.
error TS2322: Type 'Buffer' is not assignable to type 'Uint8Array<ArrayBufferLike>'.
error TS2322: Type 'Buffer' is not assignable to type 'ArrayBuffer'.
error TS2345: Argument of type 'Buffer' is not assignable to parameter of type 'string | Uint8Array<ArrayBufferLike>'.
如果你遇到 Buffer
问题,你可能首先要检查你是否使用了最新版本的 @types/node 包。这可能包括运行
Shell
npm update @types/node --save-dev
大多数时候,解决方案是指定更具体的底层缓冲区类型,而不是使用默认的 ArrayBufferLike
(即明确写出 Uint8Array<ArrayBuffer>
而不是普通的 Uint8Array
)。在某些 TypedArray
(如 Uint8Array
)传递给期望 ArrayBuffer
或 SharedArrayBuffer
的函数的情况下,你也可以尝试访问该 TypedArray
的 buffer
属性,如以下示例所示:
diff
let data = new Uint8Array([0, 1, 2, 3, 4]);
- someFunc(data)
+ someFunc(data.buffer)
类型参数推断变化
为了修复推断过程中类型变量的"泄漏",TypeScript 5.9 可能会在某些代码库中引入类型变化和可能的新错误。这些很难预测,但通常可以通过向泛型函数调用添加类型参数来修复。在此处查看更多详细信息。
下一步是什么?
现在 TypeScript 5.9 已经发布,你可能想知道下一个版本会有什么:TypeScript 6.0。
正如你可能听说的,我们最近的重点主要是 TypeScript 的原生移植,最终将作为 TypeScript 7.0 提供。那么这对 TypeScript 6.0 意味着什么呢?
我们对 TypeScript 6.0 的愿景是作为开发者调整其代码库以适应 TypeScript 7.0 的过渡点。虽然 TypeScript 6.0 可能仍会发布更新和功能,但大多数用户应该将其视为采用 TypeScript 7.0 的准备检查。这个新版本旨在与 TypeScript 7.0 保持一致,围绕某些设置引入弃用,并可能以小的方式更新类型检查行为。幸运的是,我们预测大多数项目升级到 TypeScript 6.0 不会有太多麻烦,它可能与 TypeScript 5.9 完全 API 兼容。
我们很快会有更多详细信息。这也包括 TypeScript 7.0 的详细信息,你今天就可以在 Visual Studio Code 中试用它,并直接在你的项目中安装它。
否则,我们希望 TypeScript 5.9 对你有用,并让你的日常编码成为一种乐趣。
快乐编程!
-- Daniel Rosenwasser 和 TypeScript 团队