前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >精读《Typescript 4.4》

精读《Typescript 4.4》

作者头像
黄子毅
发布2022-03-15 13:07:53
5920
发布2022-03-15 13:07:53
举报
文章被收录于专栏:前端精读评论

Typescript 4.4 正式发布了!距离 Typescript 4.5 发布还有三个月的时间,抓紧上车学习吧!

本周精读的文章:announcing-typescript-4-4

概述

更智能的自动类型收窄

类型收窄功能非常方便,它可以让 Typescript 尽可能的像 Js 一样自动智能判定类型,从而避免类型定义的工作,让你的 Typescript 写得更像 Js。

其实这个功能早就有了,在我们 精读《Typescript2.0 - 2.9》 就已经介绍过,当时用的名词是自动类型推导,这次用了更精确的自动类型收窄一词,因为只有类型收窄是安全的,比如:

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

而在 Typescript 4.4 之前的版本,如果我们将这个判定赋值给一个变量,再用到 if 分支里,就无法正常收窄类型了:

代码语言:javascript
复制
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 4.4 得到了解决,实际上是把这种类型收窄判断逻辑加深了,即无论这个判断写在哪都可以生效。所以下面这种解构的用法判断也可以推断出类型收窄:

代码语言:javascript
复制
type Shape =
    | { kind: "circle", radius: number }
    | { kind: "square", sideLength: number };

function area(shape: Shape): number {
    // Extract out the 'kind' field first.
    const { kind } = shape;

    if (kind === "circle") {
        // 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;
    }
}

不仅是单一的判断,Typescript 4.4 还支持复合类型推导:

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

mustDoWorktrue 的分支就意味着 inputAinputB 均收窄为 string 类型。

这种深层的判定还体现在,一个具备类型判断的变量进行再计算,生成的变量还具有类型判断功能:

代码语言:javascript
复制
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'.
    }
}

可以看到,我们几乎可以像写 Js 一样写 Typescript,4.4 支持了大部分符合直觉的推导非常方便。但要注意的是,Typescript 毕竟不是运行时,无法做到更彻底的自动推断,但足以支持绝大部分场景。

下标支持 Symbol 与模版字符串类型判定

原本我们定义一个用下标访问的对象是这样的:

代码语言:javascript
复制
interface Values {
  [key: string]: number
}

现在也支持 Symbol 拉:

代码语言:javascript
复制
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 allowed
let redVal = colors[red];   // 'redVal' has the type 'number'

colors[blue] = "da ba dee"; // Error: Type 'string' is not assignable to type 'number'.

而且对于特定的字符串模版也支持类型匹配,比如希望以 data- 开头的下标是一种独立类型,可以这么定义:

代码语言:javascript
复制
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'.
};

这个对于 HTML 的 data- 属性非常有帮助。

同时还支持联合类型定义,下面两种类型定义方式是等价的:

代码语言:javascript
复制
interface Data {
    [optName: string | symbol]: any;
}

// Equivalent to

interface Data {
    [optName: string]: any;
    [optName: symbol]: any;
}

更严格的错误捕获类型

unknown 类型出来之前,Typescript 以 any 作为抛出错误的默认类型,毕竟谁也不知道抛出错误的类型是什么:

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

Who knows what this might throw... 这句话很有意思,一个函数任何地方都可能出现运行时错误,这根本不是静态分析可以解决的,所以不可能自动推断错误类型,所以只能用 any

在 Typescript 4.4 的 --useUnknownInCatchVariables--strict 模式下都将以 unknown 作为捕获到错误的默认类型。

相比不存在的类型 neverunknown 仅仅是不知道是什么类型而已,所以不能像 any 一样当作任何类型使用,但我们可以将其随意推断为任意类型:

代码语言:javascript
复制
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);
    }
}

如果觉得这样做麻烦,也可以重新申明类型为 any

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

但这样做其实并不合适,因为即便是考虑了运行时因素,理论上还是可能发生意外错误,所以对错误过于自信的类型推断是不太合适的,最好保持其 unknown 类型,对所有可能的边界情况做处理。

明确的可选属性

对象的可选属性在类型描述时有个含糊不清的地方,比如:

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

其实 Typescript 对其的类型定义的是:

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

为什么要这么定义呢?因为很多情况下,没有这个 key,与这个 key 的值为 undefined 的表现是等价的。但比如 Object.keys 场景下这两种表现却又不等价,所以理论上对于 age?: number 的确切表述是:要么没有 age,要么有 age 且类型为 number,也就是说下面的写法应该是错误的:

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

在 Typescript 4.4 中同时开启 --exactOptionalPropertyTypes--strictNullChecks 即可生效。

仔细想想这是合理的,既然定义的类型不是 undefined,就算对象是可选类型,也不能认为赋值 undefined 是合理的,因为 age?: number 的心理预期是,要么没有这个 key,要么有但是类型为 number,所以当 Object.keys 发现 age 这个 key 时,值就应该是 number

支持 Static Block

Typescript 4.4 支持了 class static blocks,并且在代码块作用域内可以访问私有变量。

还有一些性能提升与体验优化杂项就不一一列举了,感兴趣可以直接看原文档:perf-improvements。

总结

从 Typescript 4.4 特性可以看出,Typescript 正在往 “更具备原生 JS 亲和性” 方向作出努力,这无疑会使 Typescript 变得越来越好用。

对更多新特性感兴趣,可以 查看 Typescript 4.5 版本发布计划。

讨论地址是:精读《Typescript 4.4》· Issue #348 · dt-fe/weekly

版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证)

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-08-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端精读评论 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
    • 更智能的自动类型收窄
      • 下标支持 Symbol 与模版字符串类型判定
        • 更严格的错误捕获类型
          • 明确的可选属性
            • 支持 Static Block
            • 总结
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档