匯入屬性 (Import Attributes)
TypeScript 5.3 支援匯入屬性提案的最新更新。
匯入屬性的一個用例是向執行時提供有關模組預期格式的資訊。
ts// We only want this to be interpreted as JSON,// not a runnable/malicious JavaScript file with a `.json` extension.import obj from "./something.json" with { type: "json" };
TypeScript 不會檢查這些屬性的內容,因為它們是宿主特定的。它們會被保留原樣,以便瀏覽器和執行時可以處理它們(並可能報錯)。
ts// TypeScript is fine with this.// But your browser? Probably not.import * as foo from "./foo.js" with { type: "fluffy bunny" };
動態 import() 呼叫也可以透過第二個引數使用匯入屬性。
tsconst obj = await import("./something.json", {with: { type: "json" }});
該第二個引數的預期型別由名為 ImportCallOptions 的型別定義,預設情況下它只期望一個名為 with 的屬性。
請注意,匯入屬性是早期提案“匯入斷言”(在 TypeScript 4.5 中實現)的演變。最明顯的區別是使用 with 關鍵字代替了 assert 關鍵字。但不太明顯的區別在於,執行時現在可以自由使用屬性來指導匯入路徑的解析和解釋,而匯入斷言只能在載入模組後斷言某些特徵。
隨著時間的推移,TypeScript 將棄用舊的匯入斷言語法,轉而採用提議的匯入屬性語法。使用 assert 的現有程式碼應遷移到 with 關鍵字。需要匯入屬性的新程式碼應僅使用 with。
我們要感謝 Oleksandr Tarasiuk 實現了此提案!同時也感謝 王文路(Wenlu Wang) 實現的匯入斷言!
在匯入型別中穩定支援 resolution-mode
在 TypeScript 4.7 中,TypeScript 在 /// <reference types="..." /> 中增加了對 resolution-mode 屬性的支援,以控制識別符號應透過 import 還是 require 語義進行解析。
ts/// <reference types="pkg" resolution-mode="require" />// or/// <reference types="pkg" resolution-mode="import" />
相應的欄位也被新增到僅型別匯入的匯入斷言中;然而,它僅在 TypeScript 的夜間構建版本中受支援。其基本原理是,從本質上講,匯入斷言並不打算用於指導模組解析。因此,該功能以實驗性的方式在夜間模式中釋出,以獲取更多反饋。
但考慮到匯入屬性可以指導解析,且我們已經看到了合理的用例,TypeScript 5.3 現在支援在 import type 中使用 resolution-mode 屬性。
ts// Resolve `pkg` as if we were importing with a `require()`import type { TypeFromRequire } from "pkg" with {"resolution-mode": "require"};// Resolve `pkg` as if we were importing with an `import`import type { TypeFromImport } from "pkg" with {"resolution-mode": "import"};export interface MergedType extends TypeFromRequire, TypeFromImport {}
這些匯入屬性也可以用於 import() 型別。
tsexport type TypeFromRequire =import("pkg", { with: { "resolution-mode": "require" } }).TypeFromRequire;export type TypeFromImport =import("pkg", { with: { "resolution-mode": "import" } }).TypeFromImport;export interface MergedType extends TypeFromRequire, TypeFromImport {}
更多資訊,請檢視此處的更改。
在所有模組模式下支援 resolution-mode
以前,使用 resolution-mode 僅允許在 moduleResolution 選項為 node16 和 nodenext 時使用。為了更容易地專門為型別目的查詢模組,resolution-mode 現在可以在所有其他 moduleResolution 選項(如 bundler、node10)中正常工作,並且在 classic 下也不會報錯。
更多資訊,請參見實現此功能的拉取請求。
switch (true) 型別收窄
TypeScript 5.3 現在可以根據 switch (true) 中每個 case 子句裡的條件進行型別收窄。
tsfunction f(x: unknown) {switch (true) {case typeof x === "string":// 'x' is a 'string' hereconsole.log(x.toUpperCase());// falls through...case Array.isArray(x):// 'x' is a 'string | any[]' here.console.log(x.length);// falls through...default:// 'x' is 'unknown' here.// ...}}
此功能是由 Mateusz Burzyński 發起的初步工作。我們要為這一貢獻表示“感謝!”。
針對布林值的比較進行型別收窄
偶爾你會發現在條件中執行與 true 或 false 的直接比較。通常這些是不必要的比較,但出於風格考慮或為了避免圍繞 JavaScript 真值(truthiness)的某些問題,你可能會偏好這樣做。無論如何,以前 TypeScript 在進行收窄時並沒有識別這些形式。
TypeScript 5.3 現在可以跟進並理解這些表示式,從而實現變數的收窄。
tsinterface A {a: string;}interface B {b: string;}type MyType = A | B;function isA(x: MyType): x is A {return "a" in x;}function someFn(x: MyType) {if (isA(x) === true) {console.log(x.a); // works!}}
我們要感謝 Mateusz Burzyński 提交了實現此功能的拉取請求。
透過 Symbol.hasInstance 進行 instanceof 收窄
JavaScript 中一個稍微晦澀的功能是,可以重寫 instanceof 運算子的行為。為此,instanceof 運算子右側的值需要具有一個由 Symbol.hasInstance 命名的特定方法。
jsclass Weirdo {static [Symbol.hasInstance](testedValue) {// wait, what?return testedValue === undefined;}}// falseconsole.log(new Thing() instanceof Weirdo);// trueconsole.log(undefined instanceof Weirdo);
為了更好地模擬 instanceof 中的這種行為,TypeScript 現在會檢查是否存在這樣的 [Symbol.hasInstance] 方法,並將其宣告為型別謂詞函式。如果存在,instanceof 運算子左側被測試的值將透過該型別謂詞進行適當的收窄。
tsinterface PointLike {x: number;y: number;}class Point implements PointLike {x: number;y: number;constructor(x: number, y: number) {this.x = x;this.y = y;}distanceFromOrigin() {return Math.sqrt(this.x ** 2 + this.y ** 2);}static [Symbol.hasInstance](val: unknown): val is PointLike {return !!val && typeof val === "object" &&"x" in val && "y" in val &&typeof val.x === "number" &&typeof val.y === "number";}}function f(value: unknown) {if (value instanceof Point) {// Can access both of these - correct!value.x;value.y;// Can't access this - we have a 'PointLike',// but we don't *actually* have a 'Point'.value.distanceFromOrigin();}}
正如你在本例中看到的,Point 定義了它自己的 [Symbol.hasInstance] 方法。它實際上充當了針對名為 PointLike 的獨立型別的自定義型別保護。在函式 f 中,我們可以使用 instanceof 將 value 收窄為 PointLike,但不能是 Point。這意味著我們可以訪問屬性 x 和 y,但不能訪問方法 distanceFromOrigin。
更多資訊,你可以在此處閱讀關於此項更改的內容。
對例項欄位上的 super 屬性訪問進行檢查
在 JavaScript 中,可以透過 super 關鍵字訪問基類中的宣告。
jsclass Base {someMethod() {console.log("Base method called!");}}class Derived extends Base {someMethod() {console.log("Derived method called!");super.someMethod();}}new Derived().someMethod();// Prints:// Derived method called!// Base method called!
這與編寫 this.someMethod() 不同,因為後者可能會呼叫被重寫的方法。這是一個微妙的區別,而當一個宣告從未被重寫時,兩者通常可以互換,這使得該區別變得更加微妙。
jsclass Base {someMethod() {console.log("someMethod called!");}}class Derived extends Base {someOtherMethod() {// These act identically.this.someMethod();super.someMethod();}}new Derived().someOtherMethod();// Prints:// someMethod called!// someMethod called!
問題在於,互換使用它們時,super 僅適用於在原型上宣告的成員 — 而非例項屬性。這意味著如果你編寫了 super.someMethod(),但 someMethod 是作為欄位定義的,你將會得到一個執行時錯誤!
tsclass Base {someMethod = () => {console.log("someMethod called!");}}class Derived extends Base {someOtherMethod() {super.someMethod();}}new Derived().someOtherMethod();// 💥// Doesn't work because 'super.someMethod' is 'undefined'.
TypeScript 5.3 現在更仔細地檢查 super 屬性訪問/方法呼叫,以檢視它們是否對應於類欄位。如果對應,我們現在將收到型別檢查錯誤。
此項檢查的貢獻者是 Jack Works!
互動式型別內嵌提示 (Inlay Hints)
TypeScript 的內嵌提示現在支援跳轉到型別的定義!這使得在程式碼中導航更加輕鬆。

詳見此處的實現。
偏好 type 自動匯入的設定
以前,當 TypeScript 為型別位置的內容生成自動匯入時,它會根據你的設定新增 type 修飾符。例如,在以下程式碼中為 Person 獲取自動匯入時:
tsexport let p: Person
TypeScript 的編輯體驗通常會新增如下匯入:
tsimport { Person } from "./types";export let p: Person
在某些設定(如 verbatimModuleSyntax)下,它會新增 type 修飾符:
tsimport { type Person } from "./types";export let p: Person
然而,也許你的程式碼庫無法使用其中一些選項;或者你只是偏好在可能的情況下使用顯式的 type 匯入。
透過近期的更改,TypeScript 現在允許將其作為編輯器特定的選項。在 Visual Studio Code 中,你可以在 UI 中的“TypeScript › Preferences: Prefer Type Only Auto Imports”下啟用它,或者作為 JSON 配置選項 typescript.preferences.preferTypeOnlyAutoImports。
透過跳過 JSDoc 解析進行最佳化
當透過 tsc 執行 TypeScript 時,編譯器現在將避免解析 JSDoc。這不僅縮短了解析時間,還減少了儲存註釋所需的記憶體使用量以及垃圾回收所花費的時間。總之,你應該會看到稍微更快的編譯速度和在 --watch 模式下更迅速的反饋。
由於並非每個使用 TypeScript 的工具都需要儲存 JSDoc(例如 typescript-eslint 和 Prettier),此解析策略已作為 API 本身的一部分提供。這使得這些工具能夠獲得我們帶給 TypeScript 編譯器的相同記憶體和速度提升。新的註釋解析策略選項在 JSDocParsingMode 中有描述。更多資訊可在此拉取請求中找到。
透過比較非規範化交集進行最佳化
在 TypeScript 中,聯合和交集始終遵循特定形式,即交集不能包含聯合型別。這意味著當我們為像 A & (B | C) 這樣的聯合建立交集時,該交集會被規範化為 (A & B) | (A & C)。不過,在某些情況下,型別系統會為了顯示目的而保留原始形式。
事實證明,原始形式可以用於型別之間一些巧妙的快速路徑比較。
例如,假設我們有 SomeType & (Type1 | Type2 | ... | Type99999NINE),並且我們想看看它是否可賦值給 SomeType。回想一下,我們的源型別其實不是一個交集 — 它是一個看起來像 (SomeType & Type1) | (SomeType & Type2) | ... |(SomeType & Type99999NINE) 的聯合。當檢查聯合是否可賦值給某個目標型別時,我們必須檢查聯合的每個成員是否都可賦值給該目標型別,這可能非常緩慢。
在 TypeScript 5.3 中,我們查看了能夠保留下來的原始交集形式。當我們比較型別時,我們會進行快速檢查,看看目標是否存在於源交集的任何組成部分中。
更多資訊,請參見此拉取請求。
tsserverlibrary.js 與 typescript.js 的合併
TypeScript 本身釋出了兩個庫檔案:tsserverlibrary.js 和 typescript.js。某些 API 僅在 tsserverlibrary.js 中可用(如 ProjectService API),這對某些匯入者可能有用。然而,這兩者是不同的包且有大量重疊,導致包中程式碼重複。更重要的是,由於自動匯入或習慣記憶,很難始終如一地使用其中一個。不小心載入兩個模組太容易了,而且程式碼可能無法在 API 的另一個例項上正常工作。即使能工作,載入第二個包也會增加資源使用量。
鑑於此,我們決定將兩者合併。typescript.js 現在包含了以前 tsserverlibrary.js 包含的內容,而 tsserverlibrary.js 現在只是簡單地重新匯出 typescript.js。比較此次合併前後的結果,我們看到了包大小的縮減如下:
| 之前 | 之後 | 差異 | 差異(百分比) | |
|---|---|---|---|---|
| 已打包 | 6.90 MiB | 5.48 MiB | -1.42 MiB | -20.61% |
| 未打包 | 38.74 MiB | 30.41 MiB | -8.33 MiB | -21.50% |
| 之前 | 之後 | 差異 | 差異(百分比) | |
|---|---|---|---|---|
lib/tsserverlibrary.d.ts |
570.95 KiB | 865.00 B | -570.10 KiB | -99.85% |
lib/tsserverlibrary.js |
8.57 MiB | 1012.00 B | -8.57 MiB | -99.99% |
lib/typescript.d.ts |
396.27 KiB | 570.95 KiB | +174.68 KiB | +44.08% |
lib/typescript.js |
7.95 MiB | 8.57 MiB | +637.53 KiB | +7.84% |
換句話說,這使得包大小減少了超過 20.5%。
更多資訊,你可以檢視此項工作的相關內容。
破壞性變更和正確性改進
lib.d.ts 變更
為 DOM 生成的型別可能會對你的程式碼庫產生影響。更多資訊,請檢視 TypeScript 5.3 的 DOM 更新。
對例項屬性上的 super 訪問進行檢查
TypeScript 5.3 現在可以檢測到 super. 屬性訪問所引用的宣告是否為類欄位,併發出錯誤提示。這可以防止執行時可能發生的錯誤。