想象一下下面的代碼:
declare const untypedCache: Map<any, any>;
function getUrlObject(urlString: string): URL {
return untypedCache.has(urlString) ?
untypedCache.get(urlString) :
urlString;
}
這段代碼的目的是從緩存中獲取 URL 對(duì)象,如果不存在就創(chuàng)建一個(gè)新的 URL 對(duì)象。但這里有個(gè)問(wèn)題:我們忘記用輸入實(shí)際構(gòu)造一個(gè)新的 URL 對(duì)象了。遺憾的是,TypeScript 通常無(wú)法捕捉到這種錯(cuò)誤。
當(dāng) TypeScript 檢查像 cond ? trueBranch : falseBranch
這樣的條件表達(dá)式時(shí),它的類(lèi)型會(huì)被視為兩個(gè)分支類(lèi)型的聯(lián)合。換句話說(shuō),它會(huì)獲取 trueBranch
和 falseBranch
的類(lèi)型,然后將它們組合成一個(gè)聯(lián)合類(lèi)型。在這個(gè)例子中,untypedCache.get(urlString)
的類(lèi)型是 any
,而 urlString
的類(lèi)型是 string
。問(wèn)題就出在這里,因?yàn)?any
類(lèi)型與其他類(lèi)型交互時(shí)會(huì)“感染”其他類(lèi)型。聯(lián)合類(lèi)型 any | string
會(huì)被簡(jiǎn)化為 any
,所以當(dāng)
TypeScript 開(kāi)始檢查 return
語(yǔ)句中的表達(dá)式是否與預(yù)期的返回類(lèi)型 URL
兼容時(shí),類(lèi)型系統(tǒng)已經(jīng)丟失了能夠捕捉到此代碼中錯(cuò)誤的信息。
在 TypeScript 5.8 中,類(lèi)型系統(tǒng)對(duì)直接位于 return
語(yǔ)句內(nèi)的條件表達(dá)式進(jìn)行了特殊處理。條件的每個(gè)分支都會(huì)被檢查是否與包含函數(shù)的聲明返回類(lèi)型(如果存在)兼容,因此類(lèi)型系統(tǒng)可以捕捉到上面示例中的錯(cuò)誤。
declare const untypedCache: Map<any, any>;
function getUrlObject(urlString: string): URL {
return untypedCache.has(urlString) ?
untypedCache.get(urlString) :
urlString; // 錯(cuò)誤!類(lèi)型“string”不能分配給類(lèi)型“URL”。
}
這一改動(dòng)是作為更廣泛的未來(lái)改進(jìn)的一部分在此拉取請(qǐng)求中完成的。
--module nodenext
下支持對(duì) ECMAScript 模塊的 require()
多年來(lái),Node.js 支持 ECMAScript 模塊(ESM)和 CommonJS 模塊共存。但兩者之間的互操作性存在一些挑戰(zhàn):
import
CommonJS 文件require()
ESM 文件換句話說(shuō),從 ESM 文件中使用 CommonJS 文件是可行的,但反過(guò)來(lái)則不行。這給希望提供 ESM 支持的庫(kù)作者帶來(lái)了許多挑戰(zhàn)。這些庫(kù)作者要么不得不與 CommonJS 用戶(hù)打破兼容性,要么“雙重發(fā)布”他們的庫(kù)(為 ESM 和 CommonJS 提供單獨(dú)的入口點(diǎn)),要么無(wú)限期地停留在 CommonJS 上。雖然雙重發(fā)布聽(tīng)起來(lái)像是一個(gè)不錯(cuò)的折中方案,但它是一個(gè)復(fù)雜且容易出錯(cuò)的過(guò)程,還會(huì)使包中的代碼量大致翻倍。
Node.js 22 放寬了一些限制,允許從 CommonJS 模塊中 require("esm")
調(diào)用 ECMAScript 模塊。Node.js 仍然不允許對(duì)包含頂級(jí) await
的 ESM 文件進(jìn)行 require()
,但大多數(shù)其他 ESM 文件現(xiàn)在可以從 CommonJS 文件中使用。這為庫(kù)作者提供了一個(gè)重大機(jī)會(huì),使他們可以在不雙重發(fā)布庫(kù)的情況下提供 ESM 支持。
TypeScript 5.8 在 --module nodenext
標(biāo)志下支持這種行為。當(dāng)啟用了 --module nodenext
時(shí),TypeScript 將避免對(duì)這些對(duì) ESM 文件的 require()
調(diào)用發(fā)出錯(cuò)誤。
由于此功能可能會(huì)回退到 Node.js 的較早版本,目前沒(méi)有穩(wěn)定的 --module nodeXXXX
選項(xiàng)啟用此行為;然而,我們預(yù)計(jì) TypeScript 的未來(lái)版本可以在 node20
下穩(wěn)定此功能。在此期間,我們鼓勵(lì)使用 Node.js 22 及更高版本的用戶(hù)使用 --module nodenext
,而庫(kù)作者和使用較早 Node.js 版本的用戶(hù)應(yīng)繼續(xù)使用 --module node16
(或進(jìn)行小更新到 --module node18
)。
有關(guān)更多信息,請(qǐng)參閱我們對(duì) require("esm") 的支持文檔。
--module node18
TypeScript 5.8 引入了一個(gè)穩(wěn)定的 --module node18
標(biāo)志。對(duì)于那些堅(jiān)持使用 Node.js 18 的用戶(hù),此標(biāo)志提供了一個(gè)穩(wěn)定的參考點(diǎn),不包含 --module nodenext
中的某些行為。具體來(lái)說(shuō):
node18
下禁止對(duì) ECMAScript 模塊的 require()
,但在 nodenext
下允許node18
下允許導(dǎo)入斷言(已棄用,改為導(dǎo)入屬性),但在 nodenext
下禁止有關(guān)更多信息,請(qǐng)參閱 --module node18
拉取請(qǐng)求以及對(duì) --module nodenext
的更改。
--erasableSyntaxOnly
選項(xiàng)最近,Node.js 23.6 取消了對(duì)直接運(yùn)行 TypeScript 文件的實(shí)驗(yàn)性支持的標(biāo)記;然而,只有在該模式下支持某些構(gòu)造。Node.js 取消了對(duì) --experimental-strip-types
的標(biāo)記,這要求任何 TypeScript 特定的語(yǔ)法不能具有運(yùn)行時(shí)語(yǔ)義。換句話說(shuō),必須能夠輕松地“擦除”或“剝離”文件中的任何 TypeScript 特定語(yǔ)法,留下一個(gè)有效的 JavaScript 文件。
這意味著像以下這樣的構(gòu)造是不被支持的:
enum
聲明namespace
和 module
import =
和 export =
賦值這里有一些不工作的示例:
// ? 錯(cuò)誤:一個(gè) `import ... = require(...)` 別名
import foo = require("foo");
// ? 錯(cuò)誤:一個(gè)具有運(yùn)行時(shí)代碼的命名空間
namespace container {}
// ? 錯(cuò)誤:一個(gè) `import =` 別名
import Bar = container.Bar;
class Point {
// ? 錯(cuò)誤:參數(shù)屬性
constructor(public x: number, public y: number) { }
}
// ? 錯(cuò)誤:一個(gè) `export =` 賦值
export = Point;
// ? 錯(cuò)誤:一個(gè)枚舉聲明
enum Direction {
Up,
Down,
Left,
Right,
}
像 ts-blank-space 或 Amaro(Node.js 中類(lèi)型剝離的底層庫(kù))這樣的類(lèi)似工具也有相同的限制。如果這些工具遇到不符合要求的代碼,它們會(huì)提供有用的錯(cuò)誤消息,但你仍然只有在實(shí)際嘗試運(yùn)行代碼時(shí)才會(huì)發(fā)現(xiàn)代碼不工作。
這就是為什么 TypeScript 5.8 引入了 --erasableSyntaxOnly
標(biāo)志。當(dāng)啟用此標(biāo)志時(shí),TypeScript 會(huì)對(duì)具有運(yùn)行時(shí)行為的大多數(shù) TypeScript 特定構(gòu)造發(fā)出錯(cuò)誤。
class C {
constructor(public x: number) { }
// ~~~~~~~~~~~~~~~~
// 錯(cuò)誤!當(dāng)啟用了 'erasableSyntaxOnly' 時(shí),此語(yǔ)法不允許。
}
通常,你會(huì)想要將此標(biāo)志與 --verbatimModuleSyntax
結(jié)合使用,這可以確保模塊包含適當(dāng)?shù)膶?dǎo)入語(yǔ)法,并且不會(huì)發(fā)生導(dǎo)入省略。
有關(guān)更多信息,請(qǐng)參閱此處的實(shí)現(xiàn)。
--libReplacement
標(biāo)志在 TypeScript 4.5 中,我們引入了用自定義文件替換默認(rèn) lib
文件的可能性。這是基于從名為 @typescript/lib-*
的包中解析庫(kù)文件的可能性。例如,你可以通過(guò)以下 package.json
將 dom
庫(kù)鎖定到 @types/web
包的特定版本:
{
"devDependencies": {
"@typescript/lib-dom": "npm:@types/web@0.0.199"
}
}
安裝后,應(yīng)該存在一個(gè)名為 @typescript/lib-dom
的包,TypeScript 在 dom
被你的設(shè)置隱含時(shí)會(huì)一直查找它。
這是一個(gè)強(qiáng)大的功能,但也增加了一些額外的工作。即使你不使用此功能,TypeScript 也會(huì)一直進(jìn)行此查找,并且必須監(jiān)視 node_modules
中的更改,以防一個(gè) lib
替換包開(kāi)始存在。
TypeScript 5.8 引入了 --libReplacement
標(biāo)志,允許你禁用此行為。如果你不使用 --libReplacement
,現(xiàn)在可以使用 --libReplacement false
禁用它。在未來(lái),--libReplacement false
可能會(huì)成為默認(rèn)值,因此如果你目前依賴(lài)此行為,應(yīng)該考慮使用 --libReplacement true
明確啟用它。
有關(guān)更多信息,請(qǐng)參閱此處的更改。
為了使類(lèi)中的計(jì)算屬性在聲明文件中具有更可預(yù)測(cè)的發(fā)出,TypeScript 5.8 將始終保留實(shí)體名稱(chēng)(bareVariables
和 dotted.names.that.look.like.this
)在類(lèi)中的計(jì)算屬性名稱(chēng)中。
例如,考慮以下代碼:
export let propName = "theAnswer";
export class MyClass {
[propName] = 42;
// ~~~~~~~~~~
// 錯(cuò)誤!類(lèi)屬性聲明中的計(jì)算屬性名稱(chēng)必須具有簡(jiǎn)單的字面量類(lèi)型或 'unique symbol' 類(lèi)型。
}
TypeScript 的早期版本在為此模塊生成聲明文件時(shí)會(huì)發(fā)出錯(cuò)誤,并且會(huì)生成一個(gè)盡力而為的聲明文件,其中包含一個(gè)索引簽名。
export declare let propName: string;
export declare class MyClass {
[x: string]: number;
}
在 TypeScript 5.8 中,示例代碼現(xiàn)在被允許,發(fā)出的聲明文件將與你編寫(xiě)的代碼匹配:
export declare let propName: string;
export declare class MyClass {
[propName]: number;
}
請(qǐng)注意,這不會(huì)在類(lèi)上創(chuàng)建靜態(tài)命名的屬性。你仍然會(huì)得到一個(gè)實(shí)際上像 [x: string]: number
這樣的索引簽名,因此對(duì)于這種情況,你需要使用 unique symbol
或字面量類(lèi)型。
請(qǐng)注意,編寫(xiě)此代碼在 --isolatedDeclarations
標(biāo)志下過(guò)去和現(xiàn)在都是錯(cuò)誤;但我們預(yù)計(jì),由于此更改,計(jì)算屬性名稱(chēng)通常將被允許在聲明發(fā)出中。
請(qǐng)注意,使用 TypeScript 5.8 編譯的文件可能會(huì)生成一個(gè)在 TypeScript 5.7 或更早版本中不向后兼容的聲明文件。
有關(guān)更多信息,請(qǐng)參閱實(shí)現(xiàn)此功能的拉取請(qǐng)求。
TypeScript 5.8 引入了許多優(yōu)化,這些優(yōu)化可以同時(shí)改進(jìn)構(gòu)建程序的時(shí)間,以及在 --watch
模式或編輯器場(chǎng)景中基于文件更改更新程序的時(shí)間。
首先,TypeScript 現(xiàn)在避免了在規(guī)范化路徑時(shí)涉及的數(shù)組分配。通常,路徑規(guī)范化會(huì)將路徑的每個(gè)部分分割成一個(gè)字符串?dāng)?shù)組,根據(jù)相對(duì)段規(guī)范化路徑,然后使用規(guī)范分隔符將它們重新連接。對(duì)于包含許多文件的項(xiàng)目,這可能是一項(xiàng)重要且重復(fù)的工作。TypeScript 現(xiàn)在避免分配數(shù)組,而是更直接地在原始路徑的索引上操作。
此外,當(dāng)進(jìn)行不會(huì)改變項(xiàng)目基本結(jié)構(gòu)的編輯時(shí),TypeScript 現(xiàn)在避免重新驗(yàn)證提供給它的選項(xiàng)(例如 tsconfig.json
的內(nèi)容)。這意味著,例如,一個(gè)簡(jiǎn)單的編輯可能不需要檢查項(xiàng)目的輸出路徑是否與輸入路徑?jīng)_突。相反,可以使用上次檢查的結(jié)果。這應(yīng)該使大型項(xiàng)目中的編輯感覺(jué)更響應(yīng)迅速。
這一部分突出了作為任何升級(jí)的一部分應(yīng)該被承認(rèn)和理解的一組值得注意的更改。有時(shí)它會(huì)突出棄用、刪除和新限制。它還可以包含功能改進(jìn)的錯(cuò)誤修復(fù),但也可能通過(guò)引入新錯(cuò)誤來(lái)影響現(xiàn)有構(gòu)建。
lib.d.ts
DOM 生成的類(lèi)型可能會(huì)對(duì)你的代碼庫(kù)的類(lèi)型檢查產(chǎn)生影響。有關(guān)更多信息,請(qǐng)參閱與此版本的 TypeScript 相關(guān)的 DOM 和 lib.d.ts
更新的問(wèn)題。
--module nodenext
下對(duì)導(dǎo)入斷言的限制導(dǎo)入斷言是 ECMAScript 的提議添加,以確保導(dǎo)入的某些屬性(例如,“此模塊是 JSON,而不是可執(zhí)行的 JavaScript 代碼”)。它們被重新設(shè)想為一個(gè)名為導(dǎo)入屬性的提議。作為過(guò)渡的一部分,它們從使用 assert
關(guān)鍵字改為使用 with
關(guān)鍵字。
// 一個(gè)導(dǎo)入斷言 ? - 與大多數(shù)運(yùn)行時(shí)不兼容。
import data from "./data.json" assert { type: "json" };
// 一個(gè)導(dǎo)入屬性 ? - 導(dǎo)入 JSON 文件的推薦方式。
import data from "./data.json" with { type: "json" };
Node.js 22 不再接受使用 assert
語(yǔ)法的導(dǎo)入斷言。因此,當(dāng)在 TypeScript 5.8 中啟用 --module nodenext
時(shí),如果遇到導(dǎo)入斷言,TypeScript 將發(fā)出錯(cuò)誤。
import data from "./data.json" assert { type: "json" };
// ~~~~~~
// 錯(cuò)誤!導(dǎo)入斷言已被導(dǎo)入屬性取代。請(qǐng)使用 'with' 而不是 'assert'。
有關(guān)更多信息,請(qǐng)參閱此處的更改。
更多建議: