Skip to content
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

TypeScript 5.3

导入属性

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

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

ts
// 我们只希望它被解释为 JSON,
// 而不是带有 `.json` 扩展名的可运行/恶意 JavaScript 文件。
import obj from "./something.json" with { type: "json" };

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

ts
// TypeScript 对此没有问题。
// 但你的浏览器呢?可能不行。
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" />

// 或

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

相应的字段也添加到了仅类型导入的导入断言中;然而,它仅在 TypeScript 的夜间版本中得到支持。其理由是,从精神上讲,导入断言并不旨在指导模块解析。因此,此功能在仅限夜间的模式下以实验性方式发布,以获取更多反馈。

但鉴于导入属性可以指导解析,并且我们已经看到了合理的用例,TypeScript 5.3 现在支持 import typeresolution-mode 属性。

ts
// 就像使用 `require()` 导入一样解析 `pkg`
import type { TypeFromRequire } from "pkg" with {
    "resolution-mode": "require"
};

// 就像使用 `import` 导入一样解析 `pkg`
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' 是 'string'
            console.log(x.toUpperCase());
            // 继续执行...

        case Array.isArray(x):
            // 这里 'x' 是 'string | any[]'
            console.log(x.length);
            // 继续执行...

        default:
          // 这里 'x' 是 'unknown'
          // ...
    }
}

此功能Mateusz Burzyński 通过初步工作牵头完成。我们要对此贡献表示“感谢!”。

与布尔值比较时的收窄

有时你可能会在条件中直接与 truefalse 进行比较。通常这些比较是不必要的,但你可能出于风格考虑而喜欢这样做,或者为了避免 JavaScript 真值相关的某些问题。无论如何,以前 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); // 有效!
    }
}

我们要感谢 Mateusz Burzyński 实现了此拉取请求

通过 Symbol.hasInstance 进行 instanceof 收窄

JavaScript 的一个略显深奥的特性是可以覆盖 instanceof 运算符的行为。为此,instanceof 运算符右侧的值需要有一个名为 Symbol.hasInstance 的特定方法。

js
class Weirdo {
    static [Symbol.hasInstance](testedValue) {
        // 等等,什么?
        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) {
        // 可以访问这两个属性 - 正确!
        value.x;
        value.y;

        // 不能访问这个方法 - 我们有一个 'PointLike',
        // 但实际上并没有一个 '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();
// 输出:
//   Derived method called!
//   Base method called!

这与编写 this.someMethod() 之类的内容不同,因为后者可能会调用被覆盖的方法。这是一个微妙的区别,由于如果声明从未被覆盖,两者通常可以互换,这使得区别更加微妙。

js
class Base {
    someMethod() {
        console.log("someMethod called!");
    }
}

class Derived extends Base {
    someOtherMethod() {
        // 这两个行为相同。
        this.someMethod();
        super.someMethod();
    }
}

new Derived().someOtherMethod();
// 输出:
//   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();
// 💥
// 不起作用,因为 'super.someMethod' 是 'undefined'。

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

此检查Jack Works 贡献!

类型的交互式内联提示

TypeScript 的内联提示现在支持跳转到类型的定义!这使得浏览代码更加方便。

按住 Ctrl 单击内联提示以跳转到参数类型的定义。

更多详情请参阅此处的实现

偏好 type 自动导入的设置

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

ts
export let p: Person

TypeScript 的编辑体验通常会为 Person 添加导入,如下所示:

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 MiB5.48 MiB-1.42 MiB-20.61%
解包后38.74 MiB30.41 MiB-8.33 MiB-21.50%
之前之后差异差异(百分比)
lib/tsserverlibrary.d.ts570.95 KiB865.00 B-570.10 KiB-99.85%
lib/tsserverlibrary.js8.57 MiB1012.00 B-8.57 MiB-99.99%
lib/typescript.d.ts396.27 KiB570.95 KiB+174.68 KiB+44.08%
lib/typescript.js7.95 MiB8.57 MiB+637.53 KiB+7.84%

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

有关更多信息,你可以在此处查看相关工作

破坏性更改和正确性改进

lib.d.ts 更改

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

对实例属性的 super 访问的检查

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

在此处查看有关此更改的更多信息