近期,TypeScript 团队宣布了 TypeScript 5.3 的 Release Candidate(RC)版本,该版本在稳定版本发布之前不再进行除了关键错误修复之外的更改。下面将介绍 TypeScript 5.3 中的一些重要功能和改进。
1. 引入导入属性
TypeScript 5.3 引入了对导入属性提案的支持。这一特性允许开发者在导入模块时添加属性,以提供关于模块预期格式的信息。例如:
import obj from "./something.json" with { type: "json" };
导入属性的内容不受 TypeScript 检查,而是由宿主环境处理。这为浏览器和运行时环境提供了更多处理导入路径的灵活性。
2. 导入类型中的 resolution-mode 支持
在 TypeScript 4.7 中,支持了在 /// <reference types="..." />
中使用 resolution-mode
属性来控制是通过 import
还是 require
语义来解析模块。 TypeScript 5.3 扩展了这一特性,使其在导入类型中同样可用。
// 使用 `require()` 解析 `pkg`
import type { TypeFromRequire } from "pkg" with {
"resolution-mode": "require"
};
// 使用 `import` 解析 `pkg`
import type { TypeFromImport } from "pkg" with {
"resolution-mode": "import"
};
这为开发者提供更多控制权,以在导入类型时指定解析模式。
3. switch (true) 的变量类型收窄
TypeScript 5.3 现在能够根据 switch (true)
中每个 case 语句中的条件对变量类型进行收窄。
function f(x: unknown) {
switch (true) {
case typeof x === "string":
// 'x' 现在是 'string' 类型
console.log(x.toUpperCase());
// 继续执行...
case Array.isArray(x):
// 'x' 现在是 'string | any[]' 类型
console.log(x.length);
// 继续执行...
default:
// 'x' 现在是 'unknown' 类型
// ...
}
}
4. 对布尔类型比较进行的类型收窄
有时,可能会在条件中直接与 true
或 false
进行比较。以前,TypeScript 在进行类型收窄时未能识别这些表达式。
interface A {
a: string;
}
interface B {
b: string;
}
type MyType = A | B;
function isA(x: MyType): x is A {
return "a" in x;
}
function someFn(x: MyType) {
if (isA(x) === true) {
console.log(x.a); // 现在可以正常工作!
}
}
TypeScript 5.3 现在支持这类表达式,能够正确进行类型收窄。
5. 通过 Symbol.hasInstance 进行 instanceof 的类型收窄
JavaScript 具有一个略显生僻的特性,即可以通过覆盖 instanceof
操作符的行为。 TypeScript 现在检查右侧值是否具有名为 Symbol.hasInstance
的特定方法,并将其声明为类型断言函数。这一特性提供更灵活的类型收窄。
class Weirdo {
static [Symbol.hasInstance](testedValue) {
return testedValue === undefined;
}
}
console.log(new Thing() instanceof Weirdo); // false
console.log(undefined instanceof Weirdo); // true
6. 检测 super 关键字在实例字段上的属性访问
在 JavaScript 中,可以通过 super
关键字访问基类中的声明。
class Base {
someMethod() {
console.log("Base method called!");
}
}
class Derived extends Base {
someMethod() {
console.log("Derived method called!");
super.someMethod();
}
}
new Derived().someMethod();
// 输出:
// Derived method called!
// Base method called!
然而,这与直接使用 this.someMethod()
有微妙的区别,尤其是在基类成员未被覆盖的情况下。
class Base {
someMethod() {
console.log("someMethod called!");
}
}
class Derived extends Base {
someOtherMethod() {
// 这两者行为相同。
this.someMethod();
super.someMethod();
}
}
new Derived().someOtherMethod();
// 输出:
// someMethod called!
// someMethod called!
TypeScript 5.3 现在更仔细地检查 super
属性访问,以查看它们是否对应于类字段。如果是,TypeScript 将发出类型检查错误,从而避免在运行时出现错误。
7. 交互式类型提示
TypeScript 的内联提示现在支持跳转到类型定义!这使得更轻松地浏览代码。
8. 设置优先使用类型自动导入
以前,当 TypeScript 为类型位置生成自动导入时,它会根据您的设置添加类型修饰符。例如,在以下代码中:
export let p: Person;
TypeScript 的编辑体验通常会添加 Person
的导入:
import { Person } from "./types";
export let p: Person;
在某些设置下,比如 verbatimModuleSyntax
,它会添加类型修饰符:
import { type Person } from "./types";
export let p: Person;
然而,现在 TypeScript 允许这成为一个编辑器特定的选项。在 Visual Studio Code 中,您可以在 UI 下启用它,路径是 “TypeScript › Preferences: Prefer Type Only Auto Imports”,或者在 JSON 配置选项 typescript.preferences.preferTypeOnlyAutoImports
中进行设置。
9. 通过跳过 JSDoc 解析进行的优化
在通过 tsc
运行 TypeScript 时,编译器现在将避免解析 JSDoc。这不仅降低了解析时间,还减少了存储注释以及在垃圾回收中花费的时间。总体来说,您应该看到稍微更快的编译速度以及在 --watch
模式下更快的反馈。
这一变化也被提供给 TypeScript API,使得使用 TypeScript 的其他工具,如 typescript-eslint 和 Prettier,也可以获得相同的内存和速度改进。
10. 通过比较非规范化交叉类型进行的优化
在 TypeScript 中,联合类型和交叉类型始终遵循特定的形式,其中交叉类型不能包含联合类型。这意味着当我们在联合类型上创建交叉类型时(如 A & (B | C)
),该交叉类型将被规范化为 (A & B) | (A & C)
。然而,在某些情况下,类型系统会保留原始形式以供显示使用。
在 TypeScript 5.3 中,原始形式可用于一些巧妙的快速路径类型比较。
例如,假设我们有 SomeType & (Type1 | Type2 | ... | Type99999NINE)
,我们想要知道这是否可分配给 SomeType
。回想一下,我们实际上没有一个交叉类型,而是一个看起来像 (SomeType & Type1) | (SomeType & Type2) | ... |(SomeType & Type99999NINE)
的联合类型。在检查联合类型是否可分配给某个目标类型时,我们必须检查联合的每个成员是否可分配给目标类型,而这可能非常慢。
在 TypeScript 5.3 中,我们查看了我们能够保存的原始交叉类型形式。当比较这些类型时,我们迅速检查目标类型是否存在于源交叉类型的任何组成部分中。
这一变化带来的好处是更快的类型检查,尤其是在处理大型联合类型时。
11. tsserverlibrary.js 和 typescript.js 之间的合并
TypeScript 本身提供了两个库文件:tsserverlibrary.js
和 typescript.js
。其中一些 API 仅在 tsserverlibrary.js
中可用(比如 ProjectService
API),对于某些导入者可能是有用的。然而,这两者是独立的捆绑包,存在很多重叠,并在包中重复代码。此外,由于自动导入或肌肉记忆的原因,一致地使用其中一个可能会具有挑战性。意外加载两个模块变得太容易,而代码在另一个 API 实例上可能无法正常工作。即使能够正常工作,加载第二个捆绑包会增加资源使用量。
鉴于此,TypeScript 决定进行合并。现在 typescript.js
包含了之前 tsserverlibrary.js
包含的内容,而 tsserverlibrary.js
现在只是重新导出 typescript.js
。在此次合并之前和之后,我们看到了包大小的明显减小:
合并前:
- Packed: 6.90 MiB
- Unpacked: 38.74 MiB
合并后:
- Packed: 5.48 MiB (-1.42 MiB, -20.61%)
- Unpacked: 30.41 MiB (-8.33 MiB, -21.50%)
这意味着包大小减小了超过 20.5%。
12. 检测实例属性上的 super 访问
TypeScript 5.3 现在会检测通过 super
关键字访问的声明是否为类字段,并在检测到时发出错误。这有助于防止在运行时发生错误。
TypeScript 5.3 带来了许多令人振奋的新功能和改进,旨在提高开发人员的工作效率,同时保持代码的可靠性和稳定性。从支持最新的导入属性提案到对交叉类型的优化,再到更强大的 super 关键字检测,每个功能都为开发人员提供更多的工具来构建健壮的代码。