likes
comments
collection
share

TypeScript 5.6 beta 发布:更完善的空值与真值检查、Iterator Helper、使用 --noCheck 跳过类型检查

作者站长头像
站长
· 阅读数 21

TypeScript 已于 2024.7.28 发布 5.6 beta 版本,你可以在 5.6 Iteration Plan 查看所有被包含的 Issue 与 PR。如果想要抢先体验新特性,执行:

$ npm install typescript@beta

来安装 beta 版本的 TypeScript,或在 VS Code 中安装 JavaScript and TypeScript Nightly ,并选择为项目使用 VS Code 的 TypeScript 版本(cmd + shift + p, 输入 select typescript version),来更新内置的 TypeScript 支持。

TypeScript 5.6 beta 发布:更完善的空值与真值检查、Iterator Helper、使用 --noCheck 跳过类型检查

更完善的空值与真值检查

TS 在 4.8 版本与 4.9 版本分别引入了「引用类型字面量值全等比较」与「NaN 相等检查」的功能,用于检查出代码中的疏漏:

const obj = {};

// Error: 此语句始终将返回 false,因为 JavaScript 中使用引用地址比较对象,而非实际值
if (obj === {}) {
}

const func = () => {};
// Error: 此表达式将始终返回 true,你是否想要调用 func ?
if(func) { }

// 此表达式将始终返回 false,你是否指 Number.isNaN(value) ?
if(value === NaN) {}

而在 5.6 版本,TS 继续完善了对这一类「可疑代码」的检查,现在能够在发现表达式计算结果始终为 TRUE 时抛出错误,如正则表达式,函数表达式等:

if (/0x[0-9a-f]/) {
  // Error: 此表达式将始终返回 true
  // ...
}

if (x => 0) {
  // Error: 此表达式将始终返回 true
  // ...
}

同时在 5.6 版本也进一步完善了对空值合并(??)语法的检查,有时候我们可能会粗心写出如下的代码:

const value = inital < input ?? 100;

我们的本意是为 input 应用默认值,但由于少了括号的分割,导致先进行左侧的比较后再尝试应用默认值。但我们知道,不同于 || 语法会在操作符左侧是 ''0false等“空值”时也应用默认值,?? 一定会确保左侧是 null / undefined 才进行默认值引用,所以这里实际上永远也不会应用默认值(虽然应用顺序也不对就是了)。

现在,TypeScript 会检查出这种情况并给出警告:

// Error: ?? 操作符的右侧无法到达,因为操作符左侧永远不会是 null/undefined
const value = inital < input ?? 100;

需要注意的是,直接使用 true / false 这样的值仍然是允许的,因为这通常是有意为之:

while (true) {
    doStuff();

    if (something()) {
        break;
    }

    doOtherStuff();
}

如果你熟悉 ESLint,应该会想到 no-constant-binary-expression 这条规则,它们的效果基本是一致的。

迭代器帮助方法 Iterator Helper

此特性是对 TC39 提案 proposal-iterator-helpers 的同步,其为 JavaScript 内置的迭代器对象(Iterator)增加了一组接口用于降低其使用成本,除 map、filter、some 这些与数组上方法功能一致的接口外,还包括一部分特有的方法:

  • iterator.take(limit: number),限定迭代器能够产生有效值的次数,超过有效次数的 next 方法调用会返回 { value: undefined, done: true },即视为迭代结束。

    function* naturals() {
      let i = 0;
      while (true) {
        yield i;
        i += 1;
      }
    }
    
    const result = naturals()
      .take(3);
    result.next(); //  {value: 0, done: false};
    result.next(); //  {value: 1, done: false};
    result.next(); //  {value: 2, done: false};
    result.next(); //  {value: undefined, done: true};
    
  • iterator.drop(limit: number),跳过迭代器的前数个值。

    function* naturals() {
      let i = 0;
      while (true) {
        yield i;
        i += 1;
      }
    }
    
    const result = naturals()
      .drop(3);
    result.next(); //  {value: 3, done: false};
    result.next(); //  {value: 4, done: false};
    result.next(); //  {value: 5, done: false};
    
  • iterator.flatMap(mapper),类似于 RxJs 中的 flatMap 操作符,mapper 方法会再次返回一个 Iterator ,可以用来将多个 Iterator 合成一个,类似于 RxJs 中合并多个 Observable。

    function* naturals() {
      let i = 0;
      while (true) {
        yield i;
        i += 1;
      }
    }
    
    const result = naturals()
      .drop(3);
    result.next(); //  {value: 3, done: false};
    result.next(); //  {value: 4, done: false};
    result.next(); //  {value: 5, done: false};
    
  • iterator.toArray(),用于将有限迭代器转换为数组。

    function* naturals() {
      let i = 0;
      while (true) {
        yield i;
        i += 1;
      }
    }
    
    const result = naturals()
      .take(5)
      .toArray();
    
    result // [0, 1, 2, 3, 4]
    
  • Iterator.from(),用于从部署了 next 方法的对象结构生成一个标准迭代器,有点类似于 Array.from 方法。

    class Iter {
      next() {
        return { done: false, value: 1 };
      }
    }
    
    const iter = new Iter();
    const wrapper = Iterator.from(iter);
    
    wrapper.next() // { value: 1, done: false }
    

这些方法明显受到了 RxJsIx 的影响,毕竟 Iterator 和 Observable 在许多方面是非常相似的。由于这些方法并不会在每个运行时中都支持,同时为了避免和已有的 Iterator 命名冲突,TypeScript 中引入了一个新的类型 BuiltinIterator 来部署这些接口。

支持任意模块标识符 Arbitrary Module Identifiers

TypeScript 现在允许使用任意的标识符名(Arbitrary Module Identifiers)称来定义模块的导出绑定:

// fruits.ts
const banana = "🍌";

export { banana as "🍌" };

// index.ts
import * as Fruits from './fruits';

Fruits['🍌'];

这一功能看起来很搞笑,但实际上,它在 ES2022 就已经得到支持,反而是 TypeScript 慢了一步。这一功能在 WASM 等场景下是有实用意义的:

import { "Foo::new" as Foo_new } from "./foo.wasm"

const foo = Foo_new()

export { Foo_new as "Foo::new" }

另外,这一功能是由 ESBuild 的作者实现的,参考 #58640

使用 --noUncheckedSideEffectImports 检查副作用导入

JavaScript 中我们是可以直接导入一个文件而不指定导入值的,比如:

import '@inside/polyfills'
import './polyfills'

这种导入一般称为副作用导入,比如导入 Polyfills,导入 CSS/Less 文件等。但是在 TypeScript 中,副作用导入的行为会略显奇怪。如果这个导入路径是确实存在的,TypeScript 会加载并检查来自导入的类型,但是如果导入路径不存在,TypeScript 会直接忽略这条导入语句而不是抛出错误,所以大概率你要到 Bundler 层或者运行时才会发现这个问题。

为了解决这个问题,TypeScript 引入了 --noUncheckedSideEffectImports 配置,在启用此配置时,TS 会检查所有的副作用导入是否有效。

使用 --noCheck 跳过类型检查

TypeScript 5.6 引入了 --noCheck 配置来支持禁用所有的类型检查——需要注意的是,此配置并不意味着不会生成声明文件(你是否在找 --noEmit ),引入其的目的之一就是配合 --isolatedDeclarations 配置,在不进行类型检查的前提下快速生成声明文件。或者你也可以独立使用 tsc --noEmittsc --noCheck 来拆分构建阶段,前者负责类型检查,后者负责生成产物。

在这里稍微展开介绍一下几个相关配置:

  • noEmit,进行类型检查,不生成类型声明与编译产物
  • noCheck,不进行类型检查,生成类型声明与编译产物
  • declaration,生成类型声明,注意这个配置默认可是 false (若启用了 Project References,则是 true)
  • emitDeclarationOnly,仅生成类型声明,不生成编译产物

全文完,我们 TS 5.7 beta 见 :-)

转载自:https://juejin.cn/post/7396650216637874187
评论
请登录