查看原文
其他

TypeScript 5.3 正式发布

小懒 FED实验室 2024-02-12
关注下方公众号,获取更多热点资讯

TypeScript 官推发布消息:TypeScript 5.3 正式发布!

TypeScript 是一种为 JavaScript 添加类型语法以实现类型检查的语言。类型检查可以捕捉各种问题,比如拼写错误和忘记检查 null 和 undefined。但是类型不仅限于类型检查 - TypeScript 的类型检查器同样可以用于丰富的编辑器工具,如自动补全、代码导航和重构。事实上,如果你一直在像 Visual Studio 或 VS Code 这样的编辑器中编写 JavaScript,这种体验就是由 TypeScript 提供支持的!

要开始使用 TypeScript,可以通过 NuGet 获取,也可以通过 npm 命令快速获取:

npm install -D typescript

以下是 TypeScript 5.3 的一些新功能!

  • 导入属性
  • 稳定支持导入类型中的 resolution-mode
  • 在所有模块模式中支持 resolution-mode
  • switch (true) 缩小范围
  • 对布尔值进行比较时的缩小范围
  • 通过 Symbol.hasInstance 对 instanceof 类型缩小
  • 检查实例字段上的 super 属性访问
  • 类型的交互式嵌入提示
  • 设置优先选择自动导入类型
  • 通过跳过 JSDoc 解析进行优化
  • 通过比较非标准交集进行优化
  • tsserverlibrary.js 和 typescript.js 之间的整合

自发布候选版本以来,添加了一项选项,以优先选择仅类型的自动导入(如果可能)。候选版本允许在各种模块解析设置中使用 resolution-mode,但没有记录下来。

1.导入属性

TypeScript 5.3 支持最新的导入属性提案更新。导入属性的一个用例是为模块的预期格式提供信息给运行时。

// 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 检查,因为它们是特定于宿主环境的,只是让浏览器和运行时处理它们(并可能会出现错误)。

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

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

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

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

请注意,导入属性是导入断言这个先前提案的一个演进,导入断言已在 TypeScript 4.5 中实现。最明显的区别是使用 with 关键字而不是 assert 关键字。但不太明显的区别是,现在运行时可以自由地使用属性来指导导入路径的解析和解释,而导入断言只能在加载模块后断言一些特点。

随着时间的推移,TypeScript 将逐步停用旧的导入断言语法,转而使用导入属性的提案语法。现有的使用 assert 的代码应该向 with 关键字迁移。需要导入属性的新代码应该只使用 with。

2.稳定支持导入类型中的 resolution-mode

在 TypeScript 4.7 中,TypeScript 在 <reference types="..." /> 中添加了对 resolution-mode 属性的支持,以控制是否应通过 import 或 require 语义解析指定符。

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

// or

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

在只输入类型的引入上,添加了与之相对应的领域用于导入断言;然而,这个功能只在 TypeScript 的每夜版本中支持。其理由是在精神上,引入断言并不意味着要指导模块的解析。因此,这个功能以试验的方式仅在每晚版本中发布,以获得更多的反馈。

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

// 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 属性也可用于 import() 类型。

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 {}

3.在所有模块模式中支持 resolution-mode

此前,只有在 moduleResolution 选项为 node16 和 nodenext 时才允许使用 resolution-mode。为了方便查找特定类型的模块,现在 resolution-mode 可以在所有其他 moduleResolution 选项(如 bundler、node10)中正常工作,而且在 classic 下也不会出错。

4.switch (true) 缩小范围

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

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.
      // ...
  }
}

5.缩小布尔值的比较范围

有时,您可能会发现自己在条件中直接比较 true 或 false。通常情况下,这些比较是不必要的,但作为一种风格,或为了避免 JavaScript 真实性方面的某些问题,您可能更喜欢这样做。无论如何,以前 TypeScript 在类型细化时无法识别这种形式。

现在,TypeScript 5.3 在缩小变量范围时可以理解这些表达式。

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!
    }
}

6.通过 Symbol.hasInstance 对 instanceof 类型缩小

实例of 通过符号.hasInstance 缩小范围 JavaScript 有一个略显神秘的特性,那就是可以覆盖 instanceof 操作符的行为。为此,instanceof 运算符右侧的值需要有一个以 Symbol.hasInstance 命名的特定方法。

JavaScript 的一个稍微有点奇特的特性是可以覆盖 instanceof 运算符的行为。要这样做,instanceof 运算符右侧的值需要具有一个由Symbol.hasInstance 命名的特定方法。

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 运算符左侧的测试值将通过该类型断言适当地缩小。

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 中,我们可以通过 instanceof 将值缩小到 PointLike,而不是 Point。这意味着我们可以访问 x 和 y 属性,但不能访问 distanceFromOrigin 方法。

7.检查实例字段上的 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();
// Prints:
//   Derived method called!
//   Base method called!

这与写 this.someMethod() 不同,因为这可能会调用一个重载方法。这是一个微妙的区别,如果一个声明从未被重载,那么这两者往往可以互换,这使得区别更加微妙。

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 被定义为一个字段,就会出现运行时错误!

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 属性访问/方法调用是否与类字段相对应。如果是,现在会得到一个类型检查错误。

8.类型的交互式嵌入提示

TypeScript 的嵌套提示现在支持跳转到类型定义!这让您可以更轻松地随意浏览代码。

9.设置优先选择自动导入类型

以前,TypeScript 在类型位置生成自动导入时,会根据设置添加一个类型修饰符。例如,当获取以下 Person 的自动导入时:

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 中,你可以在用户界面的 "TypeScript › Preferences: Prefer Type Only Auto Imports",或作为 JSON 配置选项 typescript.preferences.preferTypeOnlyAutoImports 启用。

10.通过跳过 JSDoc 解析进行优化

通过 tsc 运行 TypeScript 时,编译器将避免解析 JSDoc。这不仅减少了解析时间,还减少了存储注释的内存使用量和垃圾回收时间。总而言之,在 --watch 模式下,编译速度会略微加快,反馈也会更快。

由于并非每个使用 TypeScript 的工具都需要存储 JSDoc(例如 typescript-eslint 和 Prettier),因此这种解析策略已作为 API 本身的一部分浮出水面。这可以让这些工具获得我们为 TypeScript 编译器带来的相同内存和速度改进。JSDocParsingMode 中描述了注释解析策略的新选项。

11.通过比较非标准交集进行优化

在 TypeScript 中,联合和交集总是遵循特定的形式,其中交集不能包含联合类型。这意味着,当我们在 A & (B | C) 这样的联合类型上创建交集时,该交集将被规范化为 (A & B) | (A & C)。尽管如此,在某些情况下,类型系统仍会出于显示目的而保留原始形式。

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

例如,假设我们有 SomeType & (Type1 | Type2 | ... | Type99999NINE),我们想知道它是否可以赋值给 SomeType。回想一下,我们的源类型并不是真正的交集--我们有一个联合,看起来像 (SomeType & Type1) | (SomeType & Type2) | ...|(SomeType & Type99999NINE)。在检查一个联合体是否可赋值给某个目标类型时,我们必须检查联合体的每个成员是否都可赋值给目标类型,这可能会非常慢。

在 TypeScript 5.3 中,我们可以偷看到原来的交集形式。当我们比较类型时,我们会快速检查目标是否存在于源交集的任何成分中。

12.tsserverlibrary.js 和 typescript.js 之间的整合

TypeScript 本身有两个库文件:tsserverlibrary.js 和 typescript.js。某些 API 仅在 tsserverlibrary.js 中可用(如 ProjectService API),这可能对某些导入者有用。尽管如此,这两个不同的软件包仍有很多重叠之处,导致软件包中的代码重复。更重要的是,由于自动导入或肌肉记忆的原因,持续使用其中一个模块比另一个模块更具挑战性。意外加载两个模块太容易了,代码可能无法在不同的 API 实例上正常工作。即使能正常工作,加载第二个包也会增加资源占用。

鉴于此,TypeScript 团队决定合并这两个模块。typescript.js 现在包含了 tsserverlibrary.js 曾经包含的内容,而 tsserverlibrary.js 现在只需重新导出 typescript.js。对比合并前后的情况,我们发现软件包的大小减少了 20.5%。

未来计划

虽然 TypeScript 团队的迭代计划尚未公开,但他们已经在开发 TypeScript 5.4。预计 TypeScript 5.4 将于 2024 年 2 月底发布稳定版本。更多详细信息,敬请期待。

在此之前,TypeScript 团队希望您能尽快开始使用 TypeScript 5.3,并希望它能让您的日常编码工作充满乐趣。

大家都在看

继续滑动看下一个

TypeScript 5.3 正式发布

小懒 FED实验室
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存