Skip to content

TypeScript 5.3 发布:引入全新功能和优化

Posted on:2023年11月14日 at 08:45

近期,TypeScript 团队宣布了 TypeScript 5.3 的 Release Candidate(RC)版本,该版本在稳定版本发布之前不再进行除了关键错误修复之外的更改。下面将介绍 TypeScript 5.3 中的一些重要功能和改进。

Typescript Icon

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. 对布尔类型比较进行的类型收窄

有时,可能会在条件中直接与 truefalse 进行比较。以前,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.jstypescript.js。其中一些 API 仅在 tsserverlibrary.js 中可用(比如 ProjectService API),对于某些导入者可能是有用的。然而,这两者是独立的捆绑包,存在很多重叠,并在包中重复代码。此外,由于自动导入或肌肉记忆的原因,一致地使用其中一个可能会具有挑战性。意外加载两个模块变得太容易,而代码在另一个 API 实例上可能无法正常工作。即使能够正常工作,加载第二个捆绑包会增加资源使用量。

鉴于此,TypeScript 决定进行合并。现在 typescript.js 包含了之前 tsserverlibrary.js 包含的内容,而 tsserverlibrary.js 现在只是重新导出 typescript.js。在此次合并之前和之后,我们看到了包大小的明显减小:

合并前:

合并后:

这意味着包大小减小了超过 20.5%。

12. 检测实例属性上的 super 访问

TypeScript 5.3 现在会检测通过 super 关键字访问的声明是否为类字段,并在检测到时发出错误。这有助于防止在运行时发生错误。

TypeScript 5.3 带来了许多令人振奋的新功能和改进,旨在提高开发人员的工作效率,同时保持代码的可靠性和稳定性。从支持最新的导入属性提案到对交叉类型的优化,再到更强大的 super 关键字检测,每个功能都为开发人员提供更多的工具来构建健壮的代码。