TypeScript 3.9 更新了什么?

3 年前 · 来自专栏 来玩TypeScript啊,机都给你开好了!

类型推断的改进与 Promise.all

这里指的是对“元组到元组”的类型推断的改进(个人觉得这应该算 bug 修复,这个 bug 是 Anders 老爷亲自写出来的……)。

在过去的版本,下面的代码会导致错误,为 str 推断出的类型并不是对应位置的 "hello" ,而是所有元素类型的 union:

const [str, _] = await Promise.all(['hello', undefined]);
str.toString(); // error TS2532: Object is possibly 'undefined'.

导致这个问题的具体原因是,“数组/元组”类型是“对象”类型的一种,“对象”类型可以有“属性”、“调用签名”、“构造签名”,以及“索引签名”,在推断类型时依次进行推断。在过去,对“数组/元组”的类型推断是在“属性类型推断”这一过程中进行的,然而在后续对“索引类型推断”中覆盖了“属性类型推断”的结果,于是上述情景下便得到了“索引类型推断”的结果,也就是所有元素类型的 union。

现在 3.9 修复了这个问题,通过将“数组/元组到元组的推断”这种情况作为单独的过程提取出来。

原定的 awaited 类型被延后支持

这是一个用途非常单一的新特性,暂时延后加入了。

编译的速度提升了!

PR 36576:

在过去的版本中,对泛型形参到实参类型的映射关系是通过闭包来保存的,每个映射要创建一个函数对象,调用它得到映射结果,现在改为了用对象保存,避免创建函数对象,这个改动降低了约 8% 的内存消耗,并提升了 1% 到 9%(在老版本的 Node.js 上)的性能。

PR 36590:

对于复杂的类型,TypeScript通过缓存类型检查的结果来提高性能,3.9 优化了逻辑判断的路径,将一些开销低的判断提前。这个改动在测试中减少了约 6% 的编译时间。

PR 36607:

“类型引用”是一种语法节点,表示对“泛型类”或“泛型接口”的引用(包含了引用的符号和参数),在过去的版本中,如果“类型引用”是在“类型别名”( type 语法)的定义中立即地引用(需要立刻解出类型),那么在解该“类型引用(节点)”的类型时将为它创建“延迟类型引用(类型)”。这一设计是为了支持类型递归。

在 3.9 中,上述规则被细化为:如果“类型引用”被“类型别名”直接定义为别名(包括仅有括号或 readonly 修饰的情况),或是包含于可能存在循环引用的“类型别名”定义中,那么为它创建“延迟类型引用”。这个改动在测试中减少了约 4% 的编译时间。

PR 36622

在类型中缓存 isGenericObjectType isGenericIndexType 这两个 API 的结果。这两个 API 是用来辨别类型是否是可实例化的(泛型参数、泛型参数的映射类型、泛型参数的索引类型),因为对这些类型的“条件类型”、“索引访问类型”和“索引类型”需要延迟解决。因为传入的类型可能是元素很多的联合类型(union)或集合类型(intersection),所以增加了两个标志在类型中缓存这两个 API 的结果。 这个改动在测试中减少了约 6.5% 的编译时间。

PR 36754

改写了对“映射类型”的属性类型是否包含 undefined 类型的判断,这一部分后来又进一步改写了。总之,这个改动在测试中减少了约 8% 到 10% 的编译时间,顺便解决了一个循环引用的 bug。

PR 36696

多个对象类型如果具有不相交类型的判别属性,它们的集合类型(intersection)被简化为 never ,而在以前的版本中,结果是一个其判别属性为 never 类型的对象。这个改动在测试中略微减少了编译时间。

PR 37055

优化了模块路径解析缓存,改善了编辑器中重命名文件时自动更新 import 语句的速度。

新增 @ts-expect-error 注释

这个注释用来指示下一行代码预期应该包含错误,下一行代码的错误将不会被编译器报告。与 @ts-ignore 不同的是,如果下一行代码没有错误,该注释将产生一个错误(TS2578: Unused '@ts-expect-error' directive.)。这在对类型进行预期失败的测试时很有用。

条件表达式中未调用函数的检查

if 语句的条件中如果仅写了函数名而忘记写括号调用,编译器会报告错误。现在条件表达式 ?: 也支持这个特性了。

改进编辑器支持

改进了J avaScript 里 CommonJS 模块中的自动导入

在 JavaScript 中编写 CommonJS 模块的代码时,TypeScript 现在会自动检测正在使用的导入语句类型是 ECMAScript 风格还是 CommonJS 风格,以保持源文件风格整洁统一。

代码操作保留换行

现在在编辑器中重构代码时可以保留原代码中的空行了,例如将代码提取为函数时。

快速更正缺失返回语句

当为箭头函数加上大括号时,可以用快速更正功能自动在表达式前添加 return 关键字。

支持解决方案式(solution style)tsconfig.json 文件

通常情况下,由 TypeScript 语言服务支持的编辑器通过逐级目录向上查找 "tsconfig.json" 来确定一个文件属于哪个配置文件。如果想要编辑器正确地关联源文件和配置文件,那么配置文件必须命名为 "tsconfig.json",因此一个目录下也不能存在两个配置文件(只有名为 "tsconfig.json" 的那个才能被识别到)。

现在引入了一种新的配置文件,示例如下:

// tsconfig.json
    "files": [],
    "references": [
        { "path": "./tsconfig.shared.json" },
        { "path": "./tsconfig.frontend.json" },
        { "path": "./tsconfig.backend.json" },
}

这种实际上什么都不做的配置文件就类似于 Visual Studio 的解决方案,它引用了多个项目。语言服务可以通过这个文件,查找到源文件属于其中哪一个引用的配置文件。顺便一提,这个解决方案式配置文件仍然需要命名为 tsconfig.json,并且在源文件的同目录或父目录,但是它引用的多个配置文件可以随意放置在任意目录了。

一些破坏性更新

可选链(Optional Chaining)和非空断言(Non-Null Assertions)的解析规则变化

在之前的实现中,以下代码:

foo?.bar!.baz

被解释为等价于以下代码:

(foo?.bar).baz

也就是说,非空断言针对的是左边整个表达式部分 foo?.bar ,而不是单独的 bar 属性。这样的行为对于解析器来说简单,但对用户来说反直觉,进而容易导致类型不安全(想象一下左边有多个 ?. ,而有人不明白 ! 的作用范围)。

现在上述代码中的非空断言将只去除表达式中 bar 属性类型中的 undefined null ,在 foo undefined null 时整个表达式的值为 undefined 。如果想要之前那样的效果,需要明确地加上括号。

} 和 > 现在不再是有效的 JSX 文本字符了

为了和 spec 保持一致,TypeScript 和 Babel 现在都禁止这两个字符出现在 JSX 文本中了,需要改用 HTML 转义符(例如 <div> 2 &gt; 1 </div> ),或者插入字符串表达式(例如 <div> 2 {">"} 1 </div> ),否则会报告错误信息,还有随之而来的编辑器的快速更正建议。

对交集类型(Intersections)和可选属性(Optional Properties)更严格的检查

在以前的版本中,对于 A & B 这样的交集类型,只要 A B 的其中一个可以赋值给 C ,整个交集类型就可以赋值给 C 。然而有时遇到可选属性时会有问题,例如:

interface A {
    a: number; // notice this is 'number'
interface B {
    b: string;
interface C {
    a?: boolean; // notice this is 'boolean'
    b: string;
declare let x: A & B;
declare let y: C;