TypeScript 已于 2024.7.27 发布 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 支持。
图片
本篇是笔者的第 12 篇 TypeScript 更新日志,上一篇是 「TypeScript 5.5 beta 发布:类型守卫推导、控制流分析优化、独立类型声明等」,你可以在此账号的创作中找到(或在掘金/知乎/Twitter搜索林不渡),接下来笔者也将持续更新 TypeScript 的 DevBlog 相关,感谢你的阅读。
更完善的空值与真值检查
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,仅生成类型声明,不生成编译产物