写点什么

TypeScript 4.4 beta 版本发布

  • 2021-08-04
  • 本文字数:7452 字

    阅读完需:约 24 分钟

TypeScript 4.4 beta 版本发布

7 月 1 日,TypeScript 4.4 测试版发布了!


你可以通过 NuGet获取这个测试版,或者使用下面的 npm 命令获取:


npm install typescript@beta
复制代码


NuGet:你还可以通过以下方式获得编辑器支持:


  • 下载 Visual Studio 2019/2017

  • 遵循 Visual Studio Code 和 Sublime Text 3 的指南。


VSCode 指南:https://code.visualstudio.com/Docs/languages/typescript#_using-newer-typescript-versions


Sublime Text 指南:https://github.com/Microsoft/TypeScript-Sublime-Plugin/#note-using-different-versions-of-typescript


TypeScript 4.4 的一些主要亮点包括:


  • 用于 Aliased 条件的控制流分析

  • 符号和模板字符串模式索引签名

  • Catch 变量中的类型默认改为 unknown(--useUnknownInCatchVariables)

  • 精确的可选属性类型(--exactOptionalPropertyTypes)

  • tsc --help 更新和改进

  • 性能改进

  • JavaScript 的拼写建议

  • 嵌入提示

  • 重大更改


下面来具体解析!

用于 Aliased 条件的控制流分析

在 JavaScript 中,我们经常需要以不同的方式探测一个变量,看看它是否有我们可以使用的某个特定类型。TypeScript 理解这些检查,并将它们称为类型保护(type guards)。类型检查器使用称为控制流分析(control flow analysis)的特性来推断每种语言构造中的类型,这样就不必在我们使用每个变量时告诉 TypeScript 这个变量的类型了。


例如,我们可以这样写


function foo(arg: unknown) {    if (typeof arg === "string") {        // We know this is a string now.        console.log(arg.toUpperCase());    }}
复制代码


在这个例子中,我们检查了 arg 是否是一个 string。TypeScript 识别了 typeof arg === "string"这个检查,认为它是一个类型保护,并且能够确定 arg 应该是 if 块主体中的一个 string。但是,如果我们将条件移出为一个常量会发生什么?


function foo(arg: unknown) {    const argIsString = typeof arg === "string";    if (argIsString) {        console.log(arg.toUpperCase());        //              ~~~~~~~~~~~        // Error! Property 'toUpperCase' does not exist on type 'unknown'.    }}
复制代码


在以前的 TypeScript 版本中,这将抛出一个错误——即使 argIsString 被分配了类型保护的值也是如此,TypeScript 把这些信息丢掉了。这会很麻烦,因为我们可能想在多个位置重复使用相同的检查。为了解决这个问题,用户往往需要重复自己做过的事情或使用类型断言(cast)。在 TypeScript 4.4 中情况不再如此了。上面的例子不会再抛出错误!当 TypeScript 看到我们正在测试一个常量值时,它会做一些额外的工作来看看它是否包含类型保护。如果这个类型保护对 const、readonly 属性或未修改的参数进行操作,则 TypeScript 能够适当地缩小该值。


新版保留了各种类型保护条件——不仅仅是 typeof 检查。例如,可辨识联合类型现在很容易检查了。


type Shape =    | { kind: "circle", radius: number }    | { kind: "square", sideLength: number };    function area(shape: Shape): number {    const isCircle = shape.kind === "circle";    if (isCircle) {        // We know we have a circle here!        return Math.PI * shape.radius ** 2;    }    else {        // We know we're left with a square here!        return shape.sideLength ** 2;    }}
复制代码


再举一个例子,下面是一个检查它的两个输入是否有内容的函数。


function doSomeChecks(    inputA: string | undefined,    inputB: string | undefined,    shouldDoExtraWork: boolean,) {    let mustDoWork = inputA && inputB && shouldDoExtraWork;    if (mustDoWork) {        // Can access 'string' properties on both 'inputA' and 'inputB'!        const upperA = inputA.toUpperCase();        const upperB = inputB.toUpperCase();        // ...    }}
复制代码


如果 mustDoWork 为 true,TypeScript 就可以理解 inputA 和 inputB 都存在。这意味着我们不必编写像 inputA!这样的非空断言来告诉 TypeScript,inputA 不是 undefined 的。这里很好用的一点是这种分析是可传递的。如果我们将一个常量分配给一个包含更多常量的条件,并且这些常量都被分配了类型保护,那么 TypeScript 可以稍后传播这些条件。


function f(x: string | number | boolean) {    const isString = typeof x === "string";    const isNumber = typeof x === "number";    const isStringOrNumber = isString || isNumber;    if (isStringOrNumber) {        x;  // Type of 'x' is 'string | number'.    }    else {        x;  // Type of 'x' is 'boolean'.    }}
复制代码


请注意,这里有一个限制——TypeScript 在检查这些条件时不会一直深入下去,但它的分析对于大多数检查来说都足够深了。这个特性应该可以让很多直观的 JavaScript 代码在 TypeScript 中“正常运行”,不至于妨碍你的工作。要了解更多细节,请查看 GitHub 上的实现

符号和模板字符串模式索引签名

TypeScript 允许我们使用索引签名(index signature)来描述每个属性都必须有特定类型的对象。这允许我们将这些对象用作类似字典的类型,我们可以在其中使用字符串键,通过方括号对它们进行索引。


例如,我们可以编写一个带有索引签名的类型,该类型接受 string 键并映射到 boolean 值。如果我们尝试分配非 boolean 的值,会抛出一个错误。


interface BooleanDictionary {    [key: string]: boolean;}
declare let myDict: BooleanDictionary;
// Valid to assign boolean valuesmyDict["foo"] = true;myDict["bar"] = false;
// Error, "oops" isn't a booleanmyDict["baz"] = "oops";
复制代码


虽然 Map 在这里可能是更好的数据结构(特别是 Map<string, boolean>),但 JavaScript 对象用起来一般更方便,或者恰好是我们可以使用的对象。类似地,Array<T>已经定义了一个 number 索引签名,它允许我们插入/检索 T 类型的值。


// This is part of TypeScript's definition of the built-in Array type.interface Array<T> {    [index: number]: T;    // ...}
let arr = new Array<string>();
// Validarr[0] = "hello!";
// Error, expecting a 'string' value herearr[1] = 123;
复制代码


需要在各种地方表达大量代码的时候,索引签名非常有用;然而到目前为止,它们仅限于 string 和 number 键(并且 string 索引签名有一个人为的怪癖,它们可以接受 number 键,因为无论如何它们都会被强制转换为字符串)。这意味着 TypeScript 不允许使用 symbol 键索引对象。TypeScript 也无法对某些 string 键的子集建模索引签名——例如一个只描述“名称以文本 data-开头”的属性的索引签名。TypeScript 4.4 解决了这些限制,并允许 symbol 和模板字符串模式的索引签名。


例如,TypeScript 现在允许我们声明可以在任意 symbol 上 keyed 的类型。


interface Colors {    [sym: symbol]: number;}
const red = Symbol("red");const green = Symbol("green");const blue = Symbol("blue");
let colors: Colors = {};
colors[red] = 255; // Assignment of a number is allowedlet redVal = colors[red]; // 'redVal' has the type 'number'
colors[blue] = "da ba dee"; // Error: Type 'string' is not assignable to type 'number'.
复制代码


类似地,我们可以使用模板字符串模式类型编写索引签名。这种做法的一种可能用途是从 TypeScript 的多余属性检查中排除以 data-开头的属性。当我们将一个对象字面量传递给具有预期类型的​​内容时,TypeScript 将查找未在预期类型中声明的多余属性。


interface Options {    width?: number;    height?: number;}let a: Options = {    width: 100,    height: 100,    "data-blah": true, // Error! 'data-blah' wasn't declared in 'Options'.};interface OptionsWithDataProps extends Options {    // Permit any property starting with 'data-'.    [optName: `data-${string}`]: unknown;}let b: OptionsWithDataProps = {    width: 100,    height: 100,    "data-blah": true,       // Works!    "unknown-property": true,  // Error! 'unknown-property' wasn't declared in 'OptionsWithDataProps'.};
复制代码


关于索引签名最后还要提一下,它们现在允许联合类型,只要它们是无限域原始类型的联合即可——特别是:


  • string

  • number

  • symbol

  • 模板字符串模式(例如hello-${string}


如果一个索引签名的参数是这些类型的联合,它将脱糖为几个不同的索引签名。


interface Data {    [optName: string | symbol]: any;}// Equivalent tointerface Data {    [optName: string]: any;    [optName: symbol]: any;}
复制代码


要了解更多细节,请阅读拉取请求

Catch 变量中的类型默认改为 unknown(--useUnknownInCatchVariables)

在 JavaScript 中,任何类型的值都可以用 throw 抛出并在一个 catch 子句中捕获。因此,TypeScript 过去将 catch 子句变量类型化为 any,并且不允许其他任何类型注解:


try {    // Who knows what this might throw...    executeSomeThirdPartyCode();}catch (err) { // err: any    console.error(err.message); // Allowed, because 'any'    err.thisWillProbablyFail(); // Allowed, because 'any' :(}
复制代码


一旦 TypeScript 添加了 unknown 类型,很明显,对于想要最高程度正确性和类型安全性的用户来说,unknown 是比 catch 子句变量中的 any 更好的选项,因为它可以更好地缩小范围并迫使我们针对任意值进行测试。最终,TypeScript 4.0 允许用户在每个 catch 子句变量上指定一个 unknown(或 any)的显式类型注解,这样我们就可以根据具体情况选择更严格的类型;然而,对于某些人来说,在每个 catch 子句上手动指定:unknown 是一件苦差事。所以 TypeScript 4.4 引入了一个名为--useUnknownInCatchVariables 的新标志。此标志将 catch 子句变量的默认类型从 any 更改为 unknown。


try {    executeSomeThirdPartyCode();}catch (err) { // err: unknown    // Error! Property 'message' does not exist on type 'unknown'.    console.error(err.message);    // Works! We can narrow 'err' from 'unknown' to 'Error'.    if (err instanceof Error) {        console.error(err.message);    }}
复制代码


这个标志在--strict 选项系列下启用。这意味着如果你使用--strict 检查代码,此选项将自动打开。你可能会在 TypeScript 4.4 中遇到下面这样的错误:


Property 'message' does not exist on type 'unknown'.Property 'name' does not exist on type 'unknown'.Property 'stack' does not exist on type 'unknown'.
复制代码


如果我们不想在 catch 子句中处理 unknown 变量,我们随时可以添加一个显式的: any 注解,这样我们就可以不用更严格的类型了。


try {    executeSomeThirdPartyCode();}catch (err: any) {    console.error(err.message); // Works again!}
复制代码


要了解更多细节,请查看实现的拉取请求

精确的可选属性类型(--exactOptionalPropertyTypes)

在 JavaScript 中,读取一个对象上缺失的一个属性会产生 undefined 值。也可能有一个实际属性的值为 undefined。JavaScript 中的许多代码倾向以相同的方式处理这些情况,因此一开始 TypeScript 只是解释每个可选属性,就好像用户在类型中编写了 undefined 一样。例如,


interface Person {    name: string,    age?: number;}
复制代码


被认为等价于


interface Person {    name: string,    age?: number | undefined;}
复制代码


这意味着用户可以显式用 undefined 代替 age。


const p: Person = {    name: "Daniel",    age: undefined, // This is okay by default.};
复制代码


因此默认情况下,TypeScript 不会区分值为 undefined 的存在的属性和缺失的属性。虽然这在大多数情况下都不会出问题,但并非所有 JavaScript 代码都做出了相同的假设。Object.assign、Object.keys、objectspread({...obj})和 for-in 循环等函数和运算符的行为取决于对象上是否实际存在属性。在我们的 Person 示例中,如果在 age 的存在与否影响很大的上下文中观察到了 age 属性,这就可能会导致运行时错误。在 TypeScript 4.4 中新加入的标志--exactOptionalPropertyTypes 指定了可选属性类型应完全按照编写的方式来解释,这意味着|undefined 不会添加到类型中:


// With 'exactOptionalPropertyTypes' on:const p: Person = {    name: "Daniel",    age: undefined, // Error! undefined isn't a number};
复制代码


这个标志不是--strict 系列的一部分,如果你想要这种行为,需要显式打开它。它还需要启用--strictNullChecks。我们将更新 DefinitelyTyped 和其他一些定义,设法让过渡过程尽可能简单一些,但你还是可能会遇到一些摩擦,具体取决于你的代码结构。要了解更多细节,你可以在此处查看实现的拉取请求。

tsc --help 更新和改进

TypeScript 的--help 选项得到了更新!感谢 Song Gao 的工作,我们带来了一些更改,包括更新编译器选项的描述,并使用一些颜色和其他视觉分离途径重新设计了--help 菜单的样式。虽然我们仍在对样式做一些迭代,来让那些跨平台默认主题可以正常工作,但你可以查看原始提案来提前了解它的样貌。


原始提案:https://github.com/microsoft/TypeScript/issues/44074

性能改进

更快的声明发射

TypeScript 现在会缓存内部符号是否可在不同上下文中访问的相关信息,以及特定类型应如何打印的相关信息。这些更改可以提高有着相当复杂类型的代码的性能表现,尤其是在--declaration 标志下发射.d.ts 文件时性能提升非常明显。


此处了解更多细节。

更快的路径规范化

TypeScript 通常必须对文件路径进行多种类型的“规范化”,将它们转换为可以让编译器随处使用的一致格式。这种操作涉及用斜杠替换反斜杠,或删除路径的中间/./和/../段之类的事情。当 TypeScript 处理数以百万的文件路径时,这种规范化操作往往速度会很慢。在 TypeScript 4.4 中,路径首先要进行快速检查,查看它们究竟是否需要规范化操作。这些改进加在一起,将大型项目的加载时间减少了 5-10%,而在我们内部测试的大型项目中加载时间减少得更多。


要了解更多细节,你可以查看路径段规范化的PR以及斜杠规范化的PR

更快的路径映射

TypeScript 现在会缓存它构建路径映射的方式(使用 tsconfig.json 中的 paths 选项)。对于具有数百个映射的项目,速度提升是很明显的。在这里可以了解更多细节。

使用--strict 加快增量构建

在一个错误中,如果--strict 处于启用状态,TypeScript 最终会在--incremental 编译下重做类型检查工作。这拖慢了许多构建的速度,好像它们关闭了--incremental 一样。TypeScript 4.4 修复了这个问题,但该更改也已向后移植到了 TypeScript 4.3。


这里查看更多细节。

为大输出更快地生成源图

TypeScript 4.4 优化了超大输出文件的源映射生成速度。在构建旧版本的 TypeScript 编译器时,这一优化将发射时间减少了约 8%。


我们要向 David Michon 表示感谢,他提供了一个简单而干净的更改来实现这一性能优化。

更快的--force 构建

在项目引用上使用--build 模式时,TypeScript 必须执行更新检查以确定需要重建哪些文件。然而,当执行--force 构建时,这一信息实际上是无关紧要的,因为每个项目依赖项都将被从头开始重建。在 TypeScript 4.4 中,--force 构建避免了那些不必要的步骤,直接开始一个完整构建。在此处查看有关这一更改的更多细节。

JavaScript 的拼写建议

TypeScript 改善了 Visual Studio 和 Visual Studio Code 等编辑器中的 JavaScript 编辑体验。大多数情况下,TypeScript 试图避开 JavaScript 文件;然而,TypeScript 往往收集了很多信息,足以提出不错的建议,它也有不少方法可以提出不太有侵略性的建议。


于是 TypeScript 现在会在纯 JavaScript 文件中发出拼写建议——没有// @ts-check,或关闭了 checkJs 的项目中都会提出建议。这些建议的形式和 TypeScript 已有的“Did you mean…?”是一样的,现在它们在所有 JavaScript 文件中都可用。


这些拼写建议可以提供一个不那么干扰人的线索,告诉你你的代码是错误的。在测试这一特性时,我们设法在现有代码中找到了一些错误!


有关这一新特性的更多细节,请查看拉取请求

嵌入提示

TypeScript 正在试验对嵌入文本(inlay text)的编辑器支持,这样就可以在代码中内联显示一些有用的信息,例如参数名称。你可以将其视为一种友好的“幽灵文本”。


Visual Studio Code 中的嵌入提示预览


此特性由 Wenlu Wang 构建,他的拉取请求有更多细节。你可以在此处跟踪我们将该特性与 Visual Studio Code 集成的进度。


拉取请求:https://github.com/microsoft/TypeScript/pull/42089


跟踪进度:https://github.com/microsoft/vscode/pull/113412

重大更改

针对 TypeScript 4.4 的 lib.d.ts 更改

与每个 TypeScript 版本一样,lib.d.ts 的声明(尤其是为 Web 上下文生成的声明)已更改。你可以查阅我们的已知 lib.dom.d.ts 更改列表以了解受影响的内容。

在 Catch 变量中使用 unknown

从技术上讲,使用--strict 标志运行的用户可能会看到一些关于 catch 变量是 unknown 的新错误,特别是在现有代码假定只捕获了 Error 值的时候。这通常会导致下面这样的错误消息:


Property 'message' does not exist on type 'unknown'.Property 'name' does not exist on type 'unknown'.Property 'stack' does not exist on type 'unknown'.
复制代码


为了解决这个问题,你可以专门添加运行时检查以确保抛出的类型与你的预期类型相匹配。或者你可以只使用一个类型断言,向你的 catch 变量添加一个显式: any,或者关闭--useUnknownInCatchVariables。

抽象属性不允许初始化器

以下代码现在会抛出一个错误,因为抽象属性可能没有初始化器(initializer):


abstract class C {    abstract prop = 1;    //       ~~~~    // Property 'prop' cannot have an initializer because it is marked abstract.}
复制代码


相反,你只能为这个属性指定一个类型:


abstract class C {    abstract prop: number;}
复制代码

下一步计划

为了帮助你的团队制定计划来尝试 TypeScript 4.4,你可以阅读 4.4 迭代计划。我们目前的目标是在 8 月中旬发布一个候选版本,并在 2021 年 8 月底发布稳定版本。从现在起到我们的候选版本发布时,我们的团队将努力解决各种已知问题并听取大家的反馈,因此请下载我们新发布的测试版,告诉我们你的想法!


迭代计划:https://github.com/microsoft/TypeScript/issues/44237


编程快乐!


——Daniel Rosenwasser 和 TypeScript 团队


原文链接


https://devblogs.microsoft.com/typescript/announcing-typescript-4-4-beta/

2021-08-04 12:364362

评论 2 条评论

发布
用户头像
机翻?
2021-08-17 19:16
回复
用户头像
感觉控制流改的比较人性化
2021-08-06 21:00
回复
没有更多了
发现更多内容

多个角度论证SeekTiger 生态核心STI的魅力

西柚子

jackson学习之六:常用类注解

程序员欣宸

4月月更

RDP是什么意思?有什么用?

行云管家

运维 网络协议 RDP

重磅!百度安全参编的国家标准《信息安全技术 术语》正式发布

百度开发者中心

零信任访问控制下企业ABAC的实施问题

Geek_2d6073

“囤菜新宠”预制菜,会是生鲜电商的破局点吗?

易观分析

逃离过度努力陷阱

FunTester

FunTester 湛卢 轻松主义

杭州等保测评公司有哪些?分别叫什么?如何能查到?

行云管家

等保 等级保护 等保测评 杭州

不再单调!快来自定义你的专属背景~

优麒麟

Linux 开源 操作系统 优麒麟 用户登录

百度荣获 “2021年中国网络安全产业联盟数据安全工作委员会突出贡献奖”

百度开发者中心

如何设计帮助中心才能真正地帮助客户解决问题?

小炮

帮助中心

@所有高校师生,2022全国大学生物联网设计竞赛火热开启,限量礼品等你来拿!

HarmonyOS开发者

HarmonyOS 物联网设计竞赛

盘点近期虎符交易所上线的项目

区块链前沿News

虎符交易所

大咖说|阿里巴巴副总裁陈龙:数字技术将在绿色低碳转型中发挥关键作用

大咖说

阿里巴巴 数字化 碳中和

Tapdata PDK 生态共建计划启动!MongoDB、Doris、OceanBase、PolarDB等十余家厂商首批加入

MongoDB中文社区

SAE 联合乘云至达与谱尼测试携手共同抗疫

阿里巴巴云原生

Docker 实战教程之从入门到提高(一)

Jerry Wang

Docker Kubernetes 容器 镜像 4月月更

crontab命令详细介绍教程,快来围观

CRMEB

PHP项目微信提现功能代码详解

CRMEB

eBPF Cilium实战(2) - 底层网络可观测性

北京好雨科技有限公司

Docker Kubernetes PaaS cilium

ETL 和数仓建模的设计思路!

五分钟学大数据

4月月更

俄乌战争下的国产数据库替换思考-墨天轮

墨天轮

数据库 oracle 达梦 gbase8a

如何通过Password Vault的XSS漏洞窃取用户密码信息

喀拉峻

网络安全 XSS

易周金融观点 | 数字人民币试点扩大带动增量场景需求

易观分析

金融 数字化人民币

在Rainbond上部署高可用Apollo集群

北京好雨科技有限公司

TypeScript 4.4 beta 版本发布_语言 & 开发_Daniel Rosenwasser_InfoQ精选文章