TypeScript 5.6 beta 發(fā)布:更完善的空值與真值檢查、Iterator Helpers、支持禁用類(lèi)型檢查
TypeScript 已于 2024.7.27 發(fā)布 5.6 beta 版本,你可以在 5.6 Iteration Plan 查看所有被包含的 Issue 與 PR。如果想要搶先體驗(yàn)新特性,執(zhí)行:
$ npm install typescript@beta
來(lái)安裝 beta 版本的 TypeScript,或在 VS Code 中安裝 JavaScript and TypeScript Nightly ,并選擇為項(xiàng)目使用 VS Code 的 TypeScript 版本(cmd + shift + p, 輸入 select typescript version),來(lái)更新內(nèi)置的 TypeScript 支持。
圖片
本篇是筆者的第 12 篇 TypeScript 更新日志,上一篇是 「TypeScript 5.5 beta 發(fā)布:類(lèi)型守衛(wèi)推導(dǎo)、控制流分析優(yōu)化、獨(dú)立類(lèi)型聲明等」,你可以在此賬號(hào)的創(chuàng)作中找到(或在掘金/知乎/Twitter搜索林不渡),接下來(lái)筆者也將持續(xù)更新 TypeScript 的 DevBlog 相關(guān),感謝你的閱讀。
更完善的空值與真值檢查
TS 在 4.8 版本與 4.9 版本分別引入了「引用類(lèi)型字面量值全等比較」與「NaN 相等檢查」的功能,用于檢查出代碼中的疏漏:
const obj = {};
// Error: 此語(yǔ)句始終將返回 false,因?yàn)?JavaScript 中使用引用地址比較對(duì)象,而非實(shí)際值
if (obj === {}) {
}
const func = () => {};
// Error: 此表達(dá)式將始終返回 true,你是否想要調(diào)用 func ?
if(func) { }
// 此表達(dá)式將始終返回 false,你是否指 Number.isNaN(value) ?
if(value === NaN) {}
而在 5.6 版本,TS 繼續(xù)完善了對(duì)這一類(lèi)「可疑代碼」的檢查,現(xiàn)在能夠在發(fā)現(xiàn)表達(dá)式計(jì)算結(jié)果始終為 TRUE 時(shí)拋出錯(cuò)誤,如正則表達(dá)式,函數(shù)表達(dá)式等:
if (/0x[0-9a-f]/) {
// Error: 此表達(dá)式將始終返回 true
// ...
}
if (x => 0) {
// Error: 此表達(dá)式將始終返回 true
// ...
}
同時(shí)在 5.6 版本也進(jìn)一步完善了對(duì)空值合并(??)語(yǔ)法的檢查,有時(shí)候我們可能會(huì)粗心寫(xiě)出如下的代碼:
const value = inital < input ?? 100;
我們的本意是為 input 應(yīng)用默認(rèn)值,但由于少了括號(hào)的分割,導(dǎo)致先進(jìn)行左側(cè)的比較后再?lài)L試應(yīng)用默認(rèn)值。但我們知道,不同于 || 語(yǔ)法會(huì)在操作符左側(cè)是 '' 、 0 、false等“空值”時(shí)也應(yīng)用默認(rèn)值,?? 一定會(huì)確保左側(cè)是 null / undefined 才進(jìn)行默認(rèn)值引用,所以這里實(shí)際上永遠(yuǎn)也不會(huì)應(yīng)用默認(rèn)值(雖然應(yīng)用順序也不對(duì)就是了)。
現(xiàn)在,TypeScript 會(huì)檢查出這種情況并給出警告:
// Error: ?? 操作符的右側(cè)無(wú)法到達(dá),因?yàn)椴僮鞣髠?cè)永遠(yuǎn)不會(huì)是 null/undefined
const value = inital < input ?? 100;
需要注意的是,直接使用 true / false 這樣的值仍然是允許的,因?yàn)檫@通常是有意為之:
while (true) {
doStuff();
if (something()) {
break;
}
doOtherStuff();
}
如果你熟悉 ESLint,應(yīng)該會(huì)想到 no-constant-binary-expression 這條規(guī)則,它們的效果基本是一致的。
迭代器幫助方法 Iterator Helper
此特性是對(duì) TC39 提案 proposal-iterator-helpers 的同步,其為 JavaScript 內(nèi)置的迭代器對(duì)象(Iterator)增加了一組接口用于降低其使用成本,除 map、filter、some 這些與數(shù)組上方法功能類(lèi)似的接口外,還包括一部分特有的方法:
- iterator.take(limit: number),限定迭代器能夠產(chǎn)生有效值的次數(shù),超過(guò)有效次數(shù)的 next 方法調(diào)用會(huì)返回 { value: undefined, done: true },即視為迭代結(jié)束。
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),跳過(guò)迭代器的前數(shù)個(gè)值。
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),類(lèi)似于 RxJs 中的 flatMap 操作符,mapper 方法會(huì)再次返回一個(gè) Iterator ,可以用來(lái)將多個(gè) Iterator 合成一個(gè),類(lèi)似于 RxJs 中合并多個(gè) 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(),用于將有限迭代器轉(zhuǎn)換為數(shù)組。
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 方法的對(duì)象結(jié)構(gòu)生成一個(gè)標(biāo)準(zhǔn)迭代器,有點(diǎn)類(lèi)似于 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 在許多方面是非常相似的。由于這些方法并不會(huì)在每個(gè)運(yùn)行時(shí)中都支持,同時(shí)為了避免和已有的 Iterator 命名沖突,TypeScript 中引入了一個(gè)新的類(lèi)型 BuiltinIterator 來(lái)部署這些接口。
支持任意模塊標(biāo)識(shí)符 Arbitrary Module Identifiers
TypeScript 現(xiàn)在允許使用任意的標(biāo)識(shí)符名(Arbitrary Module Identifiers)稱(chēng)來(lái)定義模塊的導(dǎo)出綁定:
// fruits.ts
const banana = "??";
export { banana as "??" };
// index.ts
import * as Fruits from './fruits';
Fruits['??'];
這一功能看起來(lái)很搞笑,但實(shí)際上,它在 ES2022 就已經(jīng)得到支持,反而是 TypeScript 慢了一步。這一功能在 WASM 等場(chǎng)景下是有實(shí)用意義的:
import { "Foo::new" as Foo_new } from "./foo.wasm"
const foo = Foo_new()
export { Foo_new as "Foo::new" }
另外,這一功能是由 ESBuild 的作者實(shí)現(xiàn)的,參考 #58640。
使用 --noUncheckedSideEffectImports 檢查副作用導(dǎo)入
JavaScript 中我們是可以直接導(dǎo)入一個(gè)文件而不指定導(dǎo)入值的,比如:
import '@inside/polyfills'
import './polyfills'
這種導(dǎo)入一般稱(chēng)為副作用導(dǎo)入,比如導(dǎo)入 Polyfills,導(dǎo)入 CSS/Less 文件等。但是在 TypeScript 中,副作用導(dǎo)入的行為會(huì)略顯奇怪。如果這個(gè)導(dǎo)入路徑是確實(shí)存在的,TypeScript 會(huì)加載并檢查來(lái)自導(dǎo)入的類(lèi)型,但是如果導(dǎo)入路徑不存在,TypeScript 會(huì)直接忽略這條導(dǎo)入語(yǔ)句而不是拋出錯(cuò)誤,所以大概率你要到 Bundler 層或者運(yùn)行時(shí)才會(huì)發(fā)現(xiàn)這個(gè)問(wèn)題。
為了解決這個(gè)問(wèn)題,TypeScript 引入了 --noUncheckedSideEffectImports 配置,在啟用此配置時(shí),TS 會(huì)檢查所有的副作用導(dǎo)入是否有效。
使用 --noCheck 跳過(guò)類(lèi)型檢查
TypeScript 5.6 引入了 --noCheck 配置來(lái)支持禁用所有的類(lèi)型檢查——需要注意的是,此配置并不意味著不會(huì)生成聲明文件(你是否在找 --noEmit ),引入其的目的之一就是配合 --isolatedDeclarations 配置,在不進(jìn)行類(lèi)型檢查的前提下快速生成聲明文件。或者你也可以獨(dú)立使用 tsc --noEmit 與 tsc --noCheck 來(lái)拆分構(gòu)建階段,前者負(fù)責(zé)類(lèi)型檢查,后者負(fù)責(zé)生成產(chǎn)物。
在這里稍微展開(kāi)介紹一下幾個(gè)相關(guān)配置:
- noEmit,進(jìn)行類(lèi)型檢查,不生成類(lèi)型聲明與編譯產(chǎn)物
- noCheck,不進(jìn)行類(lèi)型檢查,生成類(lèi)型聲明與編譯產(chǎn)物
- declaration,生成類(lèi)型聲明,注意這個(gè)配置默認(rèn)可是 false (若啟用了 Project References,則是 true)
- emitDeclarationOnly,僅生成類(lèi)型聲明,不生成編譯產(chǎn)物