TypeScript 5.6 beta 發布:更完善的空值與真值檢查、Iterator Helpers、支持禁用類型檢查
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,僅生成類型聲明,不生成編譯產物