TypeScript 5.6 beta 发布:更完善的空值与真值检查、Iterator Helper、使用 --noCheck 跳过类型检查
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 支持。
更完善的空值与真值检查
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 应用默认值,但由于少了括号的分割,导致先进行左侧的比较后再尝试应用默认值。但我们知道,不同于 ||
语法会在操作符左侧是 ''
、 0
、false
等“空值”时也应用默认值,??
一定会确保左侧是 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 }
这些方法明显受到了 RxJs 与 Ix 的影响,毕竟 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 --noEmit
与 tsc --noCheck
来拆分构建阶段,前者负责类型检查,后者负责生成产物。
在这里稍微展开介绍一下几个相关配置:
- noEmit,进行类型检查,不生成类型声明与编译产物
- noCheck,不进行类型检查,生成类型声明与编译产物
- declaration,生成类型声明,注意这个配置默认可是 false (若启用了 Project References,则是 true)
- emitDeclarationOnly,仅生成类型声明,不生成编译产物
全文完,我们 TS 5.7 beta 见 :-)
转载自:https://juejin.cn/post/7396650216637874187