1. 首页

微软!TypeScript 3.7 RC发布,备受瞩目的Optional Chaining来了

今天,微软发布了 TypeScript 3.7 RC,这是 TypeScript 3.7 的候选发布版本。到最终版本发布之前,除了重要的错误修复,微软表示,预计不会再有其他更改。下面我们来逐一看看有哪些新功能值得关注。

TypeScript 3.7 RC 提供了一些呼声最高的功能特性!

  • 可选链

  • 空值合并

  • 断言函数

  • 更好地支持返回 never 的函数

  • (更多)递归类型别名

  • --declaration 和 –allowJs

  • 使用项目引用进行免构建编辑

  • 未调用函数的检查

  • TypeScript 文件中的 // @ts-nocheck

  • 分号格式化选项

  • 重大更改

  • DOM 更改

  • 函数真实性检查

  • 本地和导入的类型声明现在会冲突

  • API 更改

你可以通过 NuGet 或以下 npm 命令开始使用 RC 版:

npm install typescript@rc

你可以通过下列途径获得编辑器支持:

  • 下载 Visual Studio 2019/2017。

  • 查阅 Visual Studio Code 和 Sublime Text 的指引。

Visual Studio 2019/2017:https://marketplace.visualstudio.com/items?

Visual Studio Code: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 3.7 的新功能。首先是最受瞩目的功能:可选链(Optional Chaining)。

可选链

TypeScript 3.7 实现了呼声最高的 ECMAScript 功能之一:可选链(Optional Chaining)!我们的团队一直在深度参与 TC39 的标准制定,努力将这一功能推向第三阶段,从而将其带给所有的 TypeScript 用户。

那么什么是可选链呢?从本质上讲,有了可选链后,我们编写代码时如果遇到 null 或 undefined 就可以立即停止某些表达式的运行。可选链的核心是新的?. 运算符,用于 可选的属性访问。比如下面的代码:

let x = foo?.bar.baz();

也就是说,当定义了 foo 时,将计算 foo.bar. baz();但如果 foo 为 null 或 undefined,程序就会停止运行并只返回 undefined。说得更清楚些,以上代码等效下面这种写法。

let x = (foo === null || foo === undefined) ?
    undefined :
    foo.bar.baz();

请注意,如果 bar 为 null 或 undefined,我们的代码在访问 baz 时仍会出错。同样,如果 baz 为 null 或 undefined,我们在调用函数时也会出现错误。?. 只会检查其左侧的值是否为 null 或 undefined,而不检查任何后续属性。

你可能会使用?. 替换许多使用 && 运算符执行中间属性检查的代码。

// 之前
if (foo && foo.bar && foo.bar.baz) {
    // ...JS中文网 - 前端进阶资源教程 www.javascriptc.com
}

// 之后
if (foo?.bar?.baz) {
    // ...
}

请记住?. 与 && 操作的行为有所不同,其中 && 操作特别针对的是“虚假”值(例如空字符串、0、NaN 和 false)。

可选链还包括其他两个操作。首先是 可选元素访问,其作用类似可选属性访问,但允许我们访问非标识符属性(例如任意字符串、数字和符号):

/**
 * 当我们有一个数组时,返回它的第一个元素
 * 否则返回 undefined。
 */
function tryGetFirstElement<T>(arr?: T[]) {
    return arr?.[0];
    // 等效:
    // return (arr === null || arr === undefined) ?
    // undefined :
    // arr[0];
}

另一个是 可选调用,如果表达式不是 null 或 undefined,我们可以有条件地调用它们。

async function makeRequest(url: string, log?: (msg: string) => void) {
    log?.(`Request started at ${new Date().toISOString()}`);
    // 等效:
    // if (log !== null && log !== undefined) {
    // log(`Request started at ${new Date().toISOString()}`);
    // }

    const result = (await fetch(url)).json();

    log?.(`Request finished at at ${new Date().toISOString()}`);

    return result;
}

可选链的“短路(short-circuiting)”行为仅限于“普通”和可选属性访问、调用和元素访问——它不会从这些表达式进一步扩展。换句话说,下面的代码不会阻止除法或 someComputation() 调用。

let result = foo?.bar / someComputation()

它相当于:

let temp = (foo === null || foo === undefined) ?
    undefined :
    foo.bar;

let result = temp / someComputation();

这可能会导致除法结果 undefined,这就是为什么在 strictNullChecks 中,以下代码会报错:

function barPercentage(foo?: { bar: number }) {
    return foo?.bar / 100;
    // ~~~~~~~~
    // 错误:对象可能未定义。JS中文网 - 前端进阶资源教程 www.javascriptc.com
}

有关更多详细信息可以查阅提案和原始的拉取请求。

提案:https://github.com/tc39/proposal-optional-chaining/](https://github.com/tc39/proposal-optional-chaining/

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

空值合并

另一个即将推出的 ECMAScript 功能是 空值合并运算符(nullish coalescing operator),它与可选链都是我们的团队一直努力推进的功能。

你可以设想一下这个功能——?? 运算符可以在处理 null 或 undefined 时“回退”到一个默认值上。例如下面的代码:

let x = foo ?? bar();

这是一种新的途径来表示值 foo“存在”时会被使用;但当它为 null 或 undefined 时,就在其位置计算 bar()。

同样,以上代码等效于下面的写法。

let x = (foo !== null && foo !== undefined) ?
    foo :
    bar();

尝试使用默认值时,?? 运算符可以代替||。例如,以下代码段尝试获取上次保存在 local-Storage 中的 volume 值(如果曾经保存过),但因为它使用了||,所以会报错。

function initializeAudio() {
    let volume = localStorage.volume || 0.5

    // ...
}

当 localStorage.volume 设置为 0 时,页面会意外地将 volume 设置为 0.5。?? 能避免将 0、NaN 和””中的某些意外行为视为虚假值。

我们非常感谢社区成员 Wenlu Wang 和 Titian Cernicova Dragomir 实现此功能!有关更多详细信息,请查看其拉取请求和空值合并提案存储库。

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

空值合并提案存储库:https://github.com/tc39/proposal-nullish-coalescing/

断言函数

如果发生意外情况,有一组特定的函数会 throw 一个错误。它们被称为“断言”函数。例如,Node.js 为此有一个专用函数,称为 assert。

assert(someValue === 42);

在此示例中,如果 someValue 不等于 42,则 assert 将抛出 AssertionError。

JavaScript 中的断言通常用于防止传入不正确的类型。例如:

function multiply(x, y) {
    assert(typeof x === "number");
    assert(typeof y === "number");

    return x * y;
}

不幸的是,在 TypeScript 中,这些检查永远无法正确编码。这意味着 TypeScript 对松散类型的代码检查更少,并经常在稍保守的代码类型中迫使用户使用类型断言。

function yell(str) {
    assert(typeof str === "string");

    return str.toUppercase();
    // 哇!我们拼错了 'toUpperCase'。
    // 希望 TypeScript 还是能捕获它!JS中文网 - 前端进阶资源教程 www.javascriptc.com
}

JS中文网 – 前端进阶资源教程 www.javascriptC.com
一个致力于帮助开发者用代码改变世界为使命的平台,每天都可以在这里找到技术世界的头条内容

替代方法是重写代码以便 TS 语言分析,但这并不方便。

function yell(str) {
    if (typeof str !== "string") {
        throw new TypeError("str should have been a string.")
    }
    // 错误捕获!
    return str.toUppercase();
}

TypeScript 的终极目标是以破坏最少的方式嵌入现有的 JavaScript 结构中,所以 TypeScript 3.7 引入了一个称为“断言签名”的新概念,可以对这些断言函数建模。

第一种断言签名的建模方式和 Node 的 assert 函数的机制相同。它确保在作用域的其余部分中,无论检查的是什么条件都必须为真。

function assert(condition: any, msg?: string): asserts condition {
    if (!condition) {
        throw new AssertionError(msg)
    }
}

asserts condition 表示,如果 assert 返回,则传递给 condition 参数的任何内容都必须为真(否则会抛出错误)。这意味着对于作用域的其余部分,该条件必须是真的。举个例子,使用这个断言函数意味着我们 确实 捕获了之前 yell 示例中的异常。

function yell(str) {
    assert(typeof str === "string");

    return str.toUppercase();
    // ~~~~~~~~~~~
    // 错误:属性 'toUppercase' 在 'string' 类型上不存在。
    // 你说的是 'toUpperCase' 吗?
}

function assert(condition: any, msg?: string): asserts condition {
    if (!condition) {
        throw new AssertionError(msg)
    }
}

另一种断言签名不检查条件,而是告诉 TypeScript 特定的变量或属性具有不同的类型。

function assertIsString(val: any): asserts val is string {
    if (typeof val !== "string") {
        throw new AssertionError("Not a string!");
    }
}

这里的 assert val is string 确保在对 assertIs-String 进行任何调用之后,传入的任何变量都将是一个 string。

function yell(str: any) {
    assertIsString(str);

    // 现在 TypeScript 知道 'str' 是一个 'string'.

    return str.toUppercase();
    // ~~~~~~~~~~~
    // 错误:属性 'toUppercase' 在 'string' 类型上不存在。
    // 你说的是 'toUpperCase' 吗?

}

这些断言签名与编写类型谓词签名非常相似:

function isString(val: any): val is string {
    return typeof val === "string";
}

function yell(str: any) {
    if (isString(str)) {
        return str.toUppercase();
    }
    throw "Oops!";
}

就像类型谓词签名一样,这些断言签名也有着惊人的表现力。我们可以用它们表达一些相当复杂的想法。

function assertIsDefined<T>(val: T): asserts val is NonNullable<T> {
    if (val === undefined || val === null) {
        throw new AssertionError(
            `Expected 'val' to be defined, but received ${val}`
        );
    }
}

有关断言签名的更多信息,请查看原始的拉取请求。

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

更好地支持返回 never 的函数

作为断言签名项目的一部分,TypeScript 需要对调用的函数和调用的位置编码更多信息。于是我们就可以扩展对另一类函数的支持:返回 never 的函数。

任何返回 never 的函数的意图都是说它不会返回。它表明抛出了异常,出现了暂停错误条件或程序已退出。例如,@types/node 中的 process.exit(…) 被指定为返回 never。

为了确保函数永远不会返回 undefined,或者说可以从所有代码路径中有效地返回,TypeScript 需要一些语法信号——在函数结尾 return 或 throw。这样用户就会发现自己正在 return 故障函数。

function dispatch(x: string | number): SomeType {
    if (typeof x === "string") {
        return doThingWithString(x);
    }
    else if (typeof x === "number") {
        return doThingWithNumber(x);
    }
    return process.exit(1);
}

现在,当调用这些返回 never 的函数时,TypeScript 会识别出它们会影响控制流图,并说明原因。

function dispatch(x: string | number): SomeType {
    if (typeof x === "string") {
        return doThingWithString(x);
    }
    else if (typeof x === "number") {
        return doThingWithNumber(x);
    }
    process.exit(1);
}

你可以在断言函数的拉取请求中查阅更多信息。

(更多)递归类型别名

“递归”引用类型别名时一直存在一些限制。原因是一旦用到类型别名就必须能用其别名来代替自己。在某些情况下这是不可能的,因此编译器会拒绝某些递归别名,如下所示:

JS中文网 – 前端进阶资源教程 www.javascriptC.com
一个致力于帮助开发者用代码改变世界为使命的平台,每天都可以在这里找到技术世界的头条内容

type Foo = Foo;

这是一个合理的限制,因为任何对 Foo 的使用都需要用 Foo 代替,而 Foo 则需要用 Foo 代替,而 Foo 则需要用 Foo 代替……好吧,希望你懂我的意思!最后没有哪一种类型可以代替 Foo。

这与其他语言对待类型别名的方式一致,但用户使用这一功能时会出现一些意想不到的问题。例如,在 TypeScript 3.6 和更低版本中,以下代码会报错。

type ValueOrArray<T> = T | Array<ValueOrArray<T>>;
// ~~~~~~~~~~~~
// 错误:类型别名 'ValueOrArray' 循环引用它自己。

这很奇怪,因为从技术上讲这种用法并没有错,用户应该总是可以通过引入接口来编写实际上是相同的代码。

type ValueOrArray<T> = T | ArrayOfValueOrArray<T>;

interface ArrayOfValueOrArray<T> extends Array<ValueOrArray<T>> {}

由于接口(和其他对象类型)引入了一定程度的间接性,并且不需要急切地构建其完整结构,因此 TypeScript 在使用该结构时没有问题。

但对于用户而言,引入界面的解决方法并不直观。原则上来说,ValueOrArray 的原始版本,也就是直接使用 Array 确实没有任何问题。如果编译器有点“懒惰”,并且仅在必要时才计算 Array 的类型参数,则 TypeScript 就可以正确表达这些参数。

这正是 TypeScript 3.7 引入的内容。在类型别名的“顶层”,TypeScript 将推迟解析类型参数以允许使用这些模式。

这意味着像下面这样的代码(试图表示 JSON)……

type Json =
    | string
    | number
    | boolean
    | null
    | JsonObject
    | JsonArray;

interface JsonObject {
    [property: string]: Json;
}

interface JsonArray extends Array<Json> {}

终于无需辅助接口也能重写了。

type Json =
    | string
    | number
    | boolean
    | null
    | { [property: string]: Json }
    | Json[];

这种新的宽松条件也使我们可以在元组中递归引用类型别名。下面的代码以前会报错,现在则是有效的 TypeScript 代码:

type VirtualNode =
    | string
    | [string, { [key: string]: any }, ...VirtualNode[]];

const myNode: VirtualNode =
    ["div", { id: "parent" },
        ["div", { id: "first-child" }, "I'm the first child"],
        ["div", { id: "second-child" }, "I'm the second child"]
    ];

有关更多信息可以查阅原始的拉取请求。

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

–declaration 和 –allowJs

TypeScript 中的 –declaration 标志允许我们从源 TypeScript 文件(如.ts 和.tsx 文件)生成.d.ts 文件(声明文件)。这些.d.ts 文件很重要,因为它们允许 TypeScript 对其他项目进行类型检查,而无需重新检查 / 构建原始源代码。出于相同的原因,使用项目引用时 需要 此设置。

不幸的是,–declaration 不适用于 –allowJs 之类的设置,无法混合使用 TypeScript 和 JavaScript 输入文件。这是一个令人沮丧的限制,因为它意味着用户即使在迁移代码库时也无法使用 –declaration,即使使用 JSDoc 注释也是如此。TypeScript 3.7 对此做了更改,并允许将这两种功能混合使用!

使用 allowJs 时,TypeScript 将尽可能理解 JavaScript 源代码,并将其以等效表示形式保存到.d.ts 文件中。这包括其中所有的 JSDoc 注释,所以像如下代码:

/**
 * @callback Job
 * @returns {void}
 */

/** Queues work */
export class Worker {
    constructor(maxDepth = 10) {
        this.started = false;
        this.depthLimit = maxDepth;
        /**
         * 注意:队列中的作业可能会将更多项目添加到队列中
         * @type {Job[]}
         */
        this.queue = [];
    }
    /**
     * 在队列中添加一个工作项
     * @param {Job} work
     */
    push(work) {
        if (this.queue.length + 1 > this.depthLimit) throw new Error("Queue full!");
        this.queue.push(work);
    }
    /**
     * 如果队列尚未开始就启动它
     */
    start() {
        if (this.started) return false;
        this.started = true;
        while (this.queue.length) {
            /** @type {Job} */(this.queue.shift())();
        }
        return true;
    }
}

JS中文网 – 前端进阶资源教程 www.javascriptC.com
一个致力于帮助开发者用代码改变世界为使命的平台,每天都可以在这里找到技术世界的头条内容

现在将转换为以下无需实现的.d.ts 文件:

/**
 * @callback Job
 * @returns {void}
 */
/** Queues work */
export class Worker {
    constructor(maxDepth?: number);
    started: boolean;
    depthLimit: number;
    /**
     * 注意:队列中的作业可能会将更多项目添加到队列中
     * @type {Job[]}
     */
    queue: Job[];
    /**
     * 在队列中添加一个工作项
     * @param {Job} work
     */
    push(work: Job): void;
    /**
     * 如果队列尚未开始就启动它
     */
    start(): boolean;
}
export type Job = () => void;

有关更多详细信息请查阅原始拉取请求。

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

使用项目引用进行免构建编辑

TypeScript 的项目引用为我们提供了一种简便的方法来分解代码库,从而提升编译速度。不幸的是,编辑一个尚未建立依赖关系(或输出已过时)的项目时会出现很多问题。

在 TypeScript 3.7 中,当打开具有依赖项的项目时,TypeScript 将自动使用源.ts / .tsx 文件代替。这意味着使用项目引用的项目现在将获得增强的编辑体验,其中语义操作都是最新且“有效”的。你可以使用编译器选项 disableSourceOfProjectReferenceRedirect 禁用此行为,处理非常大的项目(此更改可能影响编辑性能)时用这个选项可能会很合适。

有关此更改的更多信息请查阅其拉取请求。

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

未调用函数的检查

一个常见且危险的错误是忘记调用某个函数,尤其是当函数具有零参数,或者命名更像是属性而非函数时更容易出错。

interface User {
    isAdministrator(): boolean;
    notify(): void;
    doNotDisturb?(): boolean;
}

// 稍后……

// 代码已破坏, 勿用!
function doAdminThing(user: User) {
    // oops!
    if (user.isAdministrator) {
        sudo();
        editTheConfiguration();
    }
    else {
        throw new AccessDeniedError("User is not an admin");
    }
}

比如在上面的代码中我们忘记调用 isAdmini-strator,该代码错误地允许非管理员用户编辑配置!

JS中文网 – 前端进阶资源教程 www.javascriptC.com
一个致力于帮助开发者用代码改变世界为使命的平台,每天都可以在这里找到技术世界的头条内容

在 TypeScript 3.7 中,这被标识为可能的错误:

function doAdminThing(user: User) {
    if (user.isAdministrator) {
    // ~~~~~~~~~~~~~~~~~~~~
    // 错误!这个条件将始终返回 true,因为这个函数定义是一直存在的
    // 你的意思是调用它吗?

此检查是一项重大更改,但正因如此,检查会非常保守。仅在 if 条件下才发出此错误,并且在 strictNullChecks 关闭,或者以后在 if 的正文中调用该函数时,不会在可选属性上发出此错误:

interface User {
    isAdministrator(): boolean;
    notify(): void;
    doNotDisturb?(): boolean;
}

function issueNotification(user: User) {
    if (user.doNotDisturb) {
        // OK, 属性是可选的
    }
    if (user.notify) {
        // OK, 调用了这一函数
        user.notify();
    }
}

如果你打算在不调用函数的情况下测试它,则可以将其定义更正为包含 undefined/null,或使用!! 编写类似 if(!!user.isAdministrator) 的内容以指示强制是故意的。

我们非常感谢 GitHub 用户 @jwbay,他创建了概念验证,并一直改进到现在这个版本。

TypeScript 文件中的 // @ts-nocheck

TypeScript 3.7 允许我们在 TypeScript 文件的顶部添加 // @ts-nocheck 注释以禁用语义检查。过去只有在存在 checkJs 时,JavaScript 源文件中的这一注释才会被认可。但我们已扩展了对 TypeScript 文件的支持,以简化所有用户的迁移过程。

分号格式化选项

TypeScript 的内置格式化程序现在支持在结尾分号可选的位置,根据 JavaScript 的自动分号插入(ASI)规则插入和删除分号。该设置现在在 Visual Studio Code Insiders 中可用,也在 Visual Studio 16.4 Preview 2 中的“Tools Options”菜单中可用。

微软!TypeScript 3.7 RC发布,备受瞩目的Optional Chaining来了

选择“insert”或“remove”的值还会影响自动导入的格式、提取的类型以及 TypeScript 服务提供的其他生成代码。将设置保留为默认值“ignore”会使生成的代码与当前文件中检测到的分号首选项相匹配。

重大更改

DOM更改

lib.dom.d.ts 中的类型已更新。这些更改大都是与可空性相关的正确性更改,但其影响最终取决于你的代码库。

https://github.com/microsoft/TypeScript/pull/33627

函数真实性检查

如上所述,当在 if 语句条件下似乎未调用函数时,TypeScript 现在会出错。除非满足以下任一条件,否则在 if 条件下检查函数类型时会发出错误:

  • 检查值来自可选属性。

  • strictNullChecks 已禁用。

  • 该函数稍后在 if 的正文中调用。

本地和导入的类型声明现在会起冲突

由于一个错误,之前的 TypeScript 版本允许以下构造:

// ./someOtherModule.ts
interface SomeType {
    y: string;
}

// ./myModule.ts
import { SomeType } from "./someOtherModule";
export interface SomeType {
    x: number;//JS中文网 - 前端进阶资源教程 www.javascriptC.com
}

function fn(arg: SomeType) {
    console.log(arg.x); // Error! 'x' doesn't exist on 'SomeType'
}

JS中文网 – 前端进阶资源教程 www.javascriptC.com
一个致力于帮助开发者用代码改变世界为使命的平台,每天都可以在这里找到技术世界的头条内容

在这里,SomeType 似乎同时源于 import 声明和本地 interface 声明。可能想不到的是,在模块内部 SomeType 仅引用 import 的定义,而本地声明 SomeType 仅在从另一个文件导入时才可用。这非常令人困惑,我们对这类罕见情况的调查表明,开发人员通常会认为出现了一些其他问题。

TypeScript 3.7 现在可以正确地将其识别为重复的标识符错误。正确的修复方案取决于作者的初衷,具体情况具体分析。通常来说,命名冲突是无意的,最好的解决方法是重命名导入的类型。如果初衷是要扩展导入的类型,则应编写适当的模块扩展。

API 更改

为了支持前文所述的递归类型别名模式,我们已从 TypeReference 接口中删除了 typeArguments 属性。用户应该在 TypeChecker 实例上使用 getTypeArguments 函数。

未来计划

TypeScript 3.7 的最终版本将在几周内发布,理想情况下只需极少的更改即可完成!我们希望大家尝试这个 RC 版本并向我们提供反馈,从而打造一个出色的 3.7 版本。如果你有任何建议或遇到任何问题,请随时在我们的问题跟踪器上报告问题!

https://github.com/microsoft/TypeScript/issues/new/choose

编程快乐!

——Daniel Rosenwasser 和 TypeScript 团队

原文链接:https://devblogs.microsoft.com/typescript/announcing-typescript-3-7-rc/
原文不翻墙链接:https://www.javascriptc.com/23012.html

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文来源于网络,其版权属原作者所有,如有侵权,请与小编联系,谢谢!

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:微软!TypeScript 3.7 RC发布,备受瞩目的Optional Chaining来了

链接:https://www.javascriptc.com/2301.html

原文链接:https://www.javascriptc.com/23012.html

« With the release of TypeScript 3.7 RC, it’s time to focus on Optional Chaining
微软正式发布了 Visual Studio Online»
Flutter 中文教程资源

相关推荐

QR code