前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TypeScript 4.8 发布!重点新特性解读

TypeScript 4.8 发布!重点新特性解读

作者头像
ConardLi
发布2023-01-09 17:57:26
8230
发布2023-01-09 17:57:26
举报
文章被收录于专栏:code秘密花园code秘密花园

大家好,我是 ConardLi

TypeScript 4.8 于 8 月 25 日发布正式版,本次发布带来了诸多新特性,我们一起来看几个比较重要的改动:

联合类型、交叉类型、类型收窄的优化

TypeScript 4.8 版本对 --strictNullChecks 带来了一系列准确性和一致性的改进。主要体现在联合类型、交叉类型以及类型收窄的工作方式上。

例如,unknown 在本质上其实接近联合类型 {}| null | undefined,因为它可以接受 null、undefined 和任何其他类型。TypeScript 现在可以识别这种情况,并允许从 unknown 类型赋值到 {}| null | undefined

代码语言:javascript
复制
function f(x: unknown, y: {} | null | undefined) {
    x = y; // ✅
    y = x; // ❌
}

另一个改动是 {} 与任何其他对象类型的交叉类型会简化为这个对象类型:

代码语言:javascript
复制
{} & object;  // object

另外,{} & null{} & undefined 的情况被弃用:

代码语言:javascript
复制
{} & null;  // never
{} & undefined;  // never

所以内置 NonNullable 类型被简化成了下面这样:

代码语言:javascript
复制
❌ type NonNullable<T> = T extends null | undefined ? never : T;
✅ type NonNullable<T> = T & {};

这些改进也增强了类型控制流分析的能力。例如,unknown 类型现在可以直接像 {} | null | undefined 一样类型收窄:

代码语言:javascript
复制
function narrowUnknownishUnion(x: {} | null | undefined) {
    if (x) {
        x;  // {}
    }
    else {
        x;  // {} | null | undefined
    }
}

function narrowUnknown(x: unknown) {
    if (x) {
        x;  // used to be 'unknown', now '{}'
    }
    else {
        x;  // unknown
    }
}

通用的值也会同样的类型收窄。比如当我们检查一个值是否为 nullundefined 时,就相当于让他和 {} 进行交叉,也就是和 NonNullable 的类型是一样的。所以我们现在可以定义出下面这样的函数,而不需要实现任何类型断言:

代码语言:javascript
复制
function throwIfNullable<T>(value: T): NonNullable<T> {
    if (value === undefined || value === null) {
        throw Error("ConardLi:这里需要的是 Nullable 类型!");
    }

    return value;
}

优化对模板字符串中的 infer 类型推断

infer 可以在 extends 的条件语句中推断待推断的类型,比如下面这个简单用法:

代码语言:javascript
复制
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

在这个例子中,infer R 代表待推断的返回值类型,如果 T 是一个函数,则返回函数的返回值,否则返回 any

我们再来看一个更高级的类型推断的例子:

代码语言:javascript
复制
type FirstIfString<T> =
    T extends [infer S, ...unknown[]]
        ? S extends string ? S : never
        : never;

 // string
type A = FirstIfString<[string, number, number]>;

// "hello"
type B = FirstIfString<["hello", number, number]>;

// "hello" | "world"
type C = FirstIfString<["hello" | "world", boolean]>;

// never
type D = FirstIfString<[boolean, number, string]>;

FirstIfString 是一个工具类型,意思是如果数据是一个数组,且第一个元素是一个字符串类型,就返回第一个元素,否则返回 never

这个写法上稍为有点复杂了,因为要多判断一次第一个元素是否为字符串类型,所以需要多写一次三元运算符,所以 TypeScript 4.7 引入了更简洁的语法 inferextends 可以配合使用:

代码语言:javascript
复制
type TryGetNumberIfFirst<T> =
    T extends [infer U extends number, ...unknown[]] ? U : never;

TypeScript 4.8 对在模版字符串中使用 infer extends 的情况进行了优化,下面这种情况 infer 以前会被约束为一个原始类型,现在可以推断出更精确的值:

代码语言:javascript
复制
//  'number' -> '100'.
type ConardLiNum = "100" extends `${infer U extends number}` ? U : never;

// 'bigint' -> '100n'.
type ConardLiBigInt = "100" extends `${infer U extends bigint}` ? U : never;

// 'boolean' -> 'true'.
type ConardLiBool = "true" extends `${infer U extends boolean}` ? U : never;

对象类型比较错误提示

在许多语言中,像 == 这样的操作符在对象上会执行所谓的“值”相等。例如,在 Python 中,通过使用 == 检查值是否等于空列表来检查列表是否为空:

代码语言:javascript
复制
if people_at_home == []:
    print("here's where I lie, broken inside. </3")
    adopt_animals()

但是在 JavaScript 中不是这样的,在对象之间的 ===== 检查的其实是两个对象的引用,这应该算作 JavaScript 早期的设计缺陷,所以 TypeScript 现在会对下面的代码提示错误:

代码语言:javascript
复制
if (peopleAtHome === []) {
//  ~~~~~~~~~~~~~~~~~~~
// This condition will always return 'false' since JavaScript compares objects by reference, not value.
    console.log("here's where I lie, broken inside. </3")
    adoptAnimals();
}

禁止在 JavaScript 文件中导入/导出类型

TypeScript 以前允许 JavaScript 文件在 importexport 语句中导入和导出用类型声明,但是不支持值的导入导出。这种行为是不正确的,因为在 ECMAScript 模块下,不存在的值的命名导入和导出可能会导致运行时错误。当一个 JavaScript 文件在 ——checkJs 下或通过 // @ts-check 注释进行类型检查时,TypeScript 现在会抛出错误。

代码语言:javascript
复制
// @ts-check

import { someValue, SomeType } from "some-module";

/**
 * @type {SomeType}
 */
export const myValue = someValue;

/**
 * @typedef {string | number} MyType
 */

// Will fail at runtime because 'MyType' is not a value.
export { MyType as MyExportedType };

要从另一个模块引用类型,可以直接限定导入:

代码语言:javascript
复制
❌ import { someValue, SomeType } from "some-module";
✅ import { someValue } from "some-module";
  
  /**
❌  * @type {SomeType}
✅  * @type {import("some-module").SomeType}
   */
  export const myValue = someValue;

要导出类型,只需在 JSDoc 中使用 /** @typedef */ 注释。@typedef 注释已经自动从它们包含的模块中导出类型。

代码语言:javascript
复制
  /**
   * @typedef {string | number} MyType
   */

✅ /**
✅  * @typedef {MyType} MyExportedType
✅  */
❌ export { MyType as MyExportedType };

--build、--watch、--incremental 性能优化

TypeScript 4.8 引入了几个优化,可以提升 ——watch——incremental 以及 ——build 的性能。例如,TypeScript 现在可以在 ——watch 模式避免非用户变更引发的额外变更、避免与其他可能监视 TypeScript 输出的构建工具发生冲突、以增量复用等改进。

经过实验,在一个比较大的内部代码库中,许多简单的常见操作减少了 10%-25% 的时间,而在无文件更改的情况下减少了大约 40% 的时间。下面是 TypeScript 代码库的测试情况:

参考

  • https://devblogs.microsoft.com/typescript/announcing-typescript-4-8/
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-08-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 code秘密花园 微信公众号,前往查看

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 联合类型、交叉类型、类型收窄的优化
  • 优化对模板字符串中的 infer 类型推断
  • 对象类型比较错误提示
  • 禁止在 JavaScript 文件中导入/导出类型
  • --build、--watch、--incremental 性能优化
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档