TypeScript 5.3

导入属性 (Import Attributes)

TypeScript 5.3 支持导入属性提案的最新更新。

导入属性的一个用例是向运行时提供有关模块预期格式的信息。

ts
// We only want this to be interpreted as JSON,
// not a runnable/malicious JavaScript file with a `.json` extension.
import obj from "./something.json" with { type: "json" };

TypeScript 不会检查这些属性的内容,因为它们是宿主特定的。它们会被保留原样,以便浏览器和运行时可以处理它们(并可能报错)。

ts
// TypeScript is fine with this.
// But your browser? Probably not.
import * as foo from "./foo.js" with { type: "fluffy bunny" };

动态 import() 调用也可以通过第二个参数使用导入属性。

ts
const obj = await import("./something.json", {
with: { type: "json" }
});

该第二个参数的预期类型由名为 ImportCallOptions 的类型定义,默认情况下它只期望一个名为 with 的属性。

请注意,导入属性是早期提案“导入断言”(在 TypeScript 4.5 中实现)的演变。最明显的区别是使用 with 关键字代替了 assert 关键字。但不太明显的区别在于,运行时现在可以自由使用属性来指导导入路径的解析和解释,而导入断言只能在加载模块后断言某些特征。

随着时间的推移,TypeScript 将弃用旧的导入断言语法,转而采用提议的导入属性语法。使用 assert 的现有代码应迁移到 with 关键字。需要导入属性的新代码应仅使用 with

我们要感谢 Oleksandr Tarasiuk 实现了此提案!同时也感谢 王文路(Wenlu Wang) 实现的导入断言

在导入类型中稳定支持 resolution-mode

在 TypeScript 4.7 中,TypeScript 在 /// <reference types="..." /> 中增加了对 resolution-mode 属性的支持,以控制标识符应通过 import 还是 require 语义进行解析。

ts
/// <reference types="pkg" resolution-mode="require" />
// or
/// <reference types="pkg" resolution-mode="import" />

相应的字段也被添加到仅类型导入的导入断言中;然而,它仅在 TypeScript 的夜间构建版本中受支持。其基本原理是,从本质上讲,导入断言并不打算用于指导模块解析。因此,该功能以实验性的方式在夜间模式中发布,以获取更多反馈。

但考虑到导入属性可以指导解析,且我们已经看到了合理的用例,TypeScript 5.3 现在支持在 import type 中使用 resolution-mode 属性。

ts
// Resolve `pkg` as if we were importing with a `require()`
import type { TypeFromRequire } from "pkg" with {
"resolution-mode": "require"
};
// Resolve `pkg` as if we were importing with an `import`
import type { TypeFromImport } from "pkg" with {
"resolution-mode": "import"
};
export interface MergedType extends TypeFromRequire, TypeFromImport {}

这些导入属性也可以用于 import() 类型。

ts
export type TypeFromRequire =
import("pkg", { with: { "resolution-mode": "require" } }).TypeFromRequire;
export type TypeFromImport =
import("pkg", { with: { "resolution-mode": "import" } }).TypeFromImport;
export interface MergedType extends TypeFromRequire, TypeFromImport {}

更多信息,请查看此处的更改

在所有模块模式下支持 resolution-mode

以前,使用 resolution-mode 仅允许在 moduleResolution 选项为 node16nodenext 时使用。为了更容易地专门为类型目的查找模块,resolution-mode 现在可以在所有其他 moduleResolution 选项(如 bundlernode10)中正常工作,并且在 classic 下也不会报错。

更多信息,请参见实现此功能的拉取请求

switch (true) 类型收窄

TypeScript 5.3 现在可以根据 switch (true) 中每个 case 子句里的条件进行类型收窄。

ts
function f(x: unknown) {
switch (true) {
case typeof x === "string":
// 'x' is a 'string' here
console.log(x.toUpperCase());
// falls through...
case Array.isArray(x):
// 'x' is a 'string | any[]' here.
console.log(x.length);
// falls through...
default:
// 'x' is 'unknown' here.
// ...
}
}

此功能是由 Mateusz Burzyński 发起的初步工作。我们要为这一贡献表示“感谢!”。

针对布尔值的比较进行类型收窄

偶尔你会发现在条件中执行与 truefalse 的直接比较。通常这些是不必要的比较,但出于风格考虑或为了避免围绕 JavaScript 真值(truthiness)的某些问题,你可能会偏好这样做。无论如何,以前 TypeScript 在进行收窄时并没有识别这些形式。

TypeScript 5.3 现在可以跟进并理解这些表达式,从而实现变量的收窄。

ts
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); // works!
}
}

我们要感谢 Mateusz Burzyński 提交了实现此功能的拉取请求

通过 Symbol.hasInstance 进行 instanceof 收窄

JavaScript 中一个稍微晦涩的功能是,可以重写 instanceof 运算符的行为。为此,instanceof 运算符右侧的值需要具有一个由 Symbol.hasInstance 命名的特定方法。

js
class Weirdo {
static [Symbol.hasInstance](testedValue) {
// wait, what?
return testedValue === undefined;
}
}
// false
console.log(new Thing() instanceof Weirdo);
// true
console.log(undefined instanceof Weirdo);

为了更好地模拟 instanceof 中的这种行为,TypeScript 现在会检查是否存在这样的 [Symbol.hasInstance] 方法,并将其声明为类型谓词函数。如果存在,instanceof 运算符左侧被测试的值将通过该类型谓词进行适当的收窄。

ts
interface PointLike {
x: number;
y: number;
}
class Point implements PointLike {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
distanceFromOrigin() {
return Math.sqrt(this.x ** 2 + this.y ** 2);
}
static [Symbol.hasInstance](val: unknown): val is PointLike {
return !!val && typeof val === "object" &&
"x" in val && "y" in val &&
typeof val.x === "number" &&
typeof val.y === "number";
}
}
function f(value: unknown) {
if (value instanceof Point) {
// Can access both of these - correct!
value.x;
value.y;
// Can't access this - we have a 'PointLike',
// but we don't *actually* have a 'Point'.
value.distanceFromOrigin();
}
}

正如你在本例中看到的,Point 定义了它自己的 [Symbol.hasInstance] 方法。它实际上充当了针对名为 PointLike 的独立类型的自定义类型保护。在函数 f 中,我们可以使用 instanceofvalue 收窄为 PointLike,但不能Point。这意味着我们可以访问属性 xy,但不能访问方法 distanceFromOrigin

更多信息,你可以在此处阅读关于此项更改的内容

对实例字段上的 super 属性访问进行检查

在 JavaScript 中,可以通过 super 关键字访问基类中的声明。

js
class Base {
someMethod() {
console.log("Base method called!");
}
}
class Derived extends Base {
someMethod() {
console.log("Derived method called!");
super.someMethod();
}
}
new Derived().someMethod();
// Prints:
// Derived method called!
// Base method called!

这与编写 this.someMethod() 不同,因为后者可能会调用被重写的方法。这是一个微妙的区别,而当一个声明从未被重写时,两者通常可以互换,这使得该区别变得更加微妙。

js
class Base {
someMethod() {
console.log("someMethod called!");
}
}
class Derived extends Base {
someOtherMethod() {
// These act identically.
this.someMethod();
super.someMethod();
}
}
new Derived().someOtherMethod();
// Prints:
// someMethod called!
// someMethod called!

问题在于,互换使用它们时,super 仅适用于在原型上声明的成员 — 而非实例属性。这意味着如果你编写了 super.someMethod(),但 someMethod 是作为字段定义的,你将会得到一个运行时错误!

ts
class Base {
someMethod = () => {
console.log("someMethod called!");
}
}
class Derived extends Base {
someOtherMethod() {
super.someMethod();
}
}
new Derived().someOtherMethod();
// 💥
// Doesn't work because 'super.someMethod' is 'undefined'.

TypeScript 5.3 现在更仔细地检查 super 属性访问/方法调用,以查看它们是否对应于类字段。如果对应,我们现在将收到类型检查错误。

此项检查的贡献者是 Jack Works

交互式类型内嵌提示 (Inlay Hints)

TypeScript 的内嵌提示现在支持跳转到类型的定义!这使得在代码中导航更加轻松。

Ctrl-clicking an inlay hint to jump to the definition of a parameter type.

详见此处的实现

偏好 type 自动导入的设置

以前,当 TypeScript 为类型位置的内容生成自动导入时,它会根据你的设置添加 type 修饰符。例如,在以下代码中为 Person 获取自动导入时:

ts
export let p: Person

TypeScript 的编辑体验通常会添加如下导入:

ts
import { Person } from "./types";
export let p: Person

在某些设置(如 verbatimModuleSyntax)下,它会添加 type 修饰符:

ts
import { type Person } from "./types";
export let p: Person

然而,也许你的代码库无法使用其中一些选项;或者你只是偏好在可能的情况下使用显式的 type 导入。

通过近期的更改,TypeScript 现在允许将其作为编辑器特定的选项。在 Visual Studio Code 中,你可以在 UI 中的“TypeScript › Preferences: Prefer Type Only Auto Imports”下启用它,或者作为 JSON 配置选项 typescript.preferences.preferTypeOnlyAutoImports

通过跳过 JSDoc 解析进行优化

当通过 tsc 运行 TypeScript 时,编译器现在将避免解析 JSDoc。这不仅缩短了解析时间,还减少了存储注释所需的内存使用量以及垃圾回收所花费的时间。总之,你应该会看到稍微更快的编译速度和在 --watch 模式下更迅速的反馈。

具体更改可在此处查看。.

由于并非每个使用 TypeScript 的工具都需要存储 JSDoc(例如 typescript-eslint 和 Prettier),此解析策略已作为 API 本身的一部分提供。这使得这些工具能够获得我们带给 TypeScript 编译器的相同内存和速度提升。新的注释解析策略选项在 JSDocParsingMode 中有描述。更多信息可在此拉取请求中找到。

通过比较非规范化交集进行优化

在 TypeScript 中,联合和交集始终遵循特定形式,即交集不能包含联合类型。这意味着当我们为像 A & (B | C) 这样的联合创建交集时,该交集会被规范化为 (A & B) | (A & C)。不过,在某些情况下,类型系统会为了显示目的而保留原始形式。

事实证明,原始形式可以用于类型之间一些巧妙的快速路径比较。

例如,假设我们有 SomeType & (Type1 | Type2 | ... | Type99999NINE),并且我们想看看它是否可赋值给 SomeType。回想一下,我们的源类型其实不是一个交集 — 它是一个看起来像 (SomeType & Type1) | (SomeType & Type2) | ... |(SomeType & Type99999NINE) 的联合。当检查联合是否可赋值给某个目标类型时,我们必须检查联合的每个成员是否都可赋值给该目标类型,这可能非常缓慢。

在 TypeScript 5.3 中,我们查看了能够保留下来的原始交集形式。当我们比较类型时,我们会进行快速检查,看看目标是否存在于源交集的任何组成部分中。

更多信息,请参见此拉取请求

tsserverlibrary.jstypescript.js 的合并

TypeScript 本身发布了两个库文件:tsserverlibrary.jstypescript.js。某些 API 仅在 tsserverlibrary.js 中可用(如 ProjectService API),这对某些导入者可能有用。然而,这两者是不同的包且有大量重叠,导致包中代码重复。更重要的是,由于自动导入或习惯记忆,很难始终如一地使用其中一个。不小心加载两个模块太容易了,而且代码可能无法在 API 的另一个实例上正常工作。即使能工作,加载第二个包也会增加资源使用量。

鉴于此,我们决定将两者合并。typescript.js 现在包含了以前 tsserverlibrary.js 包含的内容,而 tsserverlibrary.js 现在只是简单地重新导出 typescript.js。比较此次合并前后的结果,我们看到了包大小的缩减如下:

之前 之后 差异 差异(百分比)
已打包 6.90 MiB 5.48 MiB -1.42 MiB -20.61%
未打包 38.74 MiB 30.41 MiB -8.33 MiB -21.50%
之前 之后 差异 差异(百分比)
lib/tsserverlibrary.d.ts 570.95 KiB 865.00 B -570.10 KiB -99.85%
lib/tsserverlibrary.js 8.57 MiB 1012.00 B -8.57 MiB -99.99%
lib/typescript.d.ts 396.27 KiB 570.95 KiB +174.68 KiB +44.08%
lib/typescript.js 7.95 MiB 8.57 MiB +637.53 KiB +7.84%

换句话说,这使得包大小减少了超过 20.5%。

更多信息,你可以查看此项工作的相关内容

破坏性变更和正确性改进

lib.d.ts 变更

为 DOM 生成的类型可能会对你的代码库产生影响。更多信息,请查看 TypeScript 5.3 的 DOM 更新

对实例属性上的 super 访问进行检查

TypeScript 5.3 现在可以检测到 super. 属性访问所引用的声明是否为类字段,并发出错误提示。这可以防止运行时可能发生的错误。

在此处查看关于此更改的更多信息。.

TypeScript 文档是一个开源项目。请通过发送 Pull Request 帮助我们改进这些页面

此页面的贡献者
ABAndrew Branch (6)
ELEliran Levi (1)

最后更新:2026 年 3 月 27 日