允許在建構函式中 super() 之前編寫程式碼
在 JavaScript 類中,必須在引用 this 之前呼叫 super()。TypeScript 也強制執行這一點,儘管它在確保這一點的方式上有點過於嚴格。在 TypeScript 中,如果一個類的建構函式包含任何屬性初始化器,那麼在建構函式的開頭包含任何程式碼以前都是錯誤的。
tsclass Base {// ...}class Derived extends Base {someProperty = true;constructor() {// error!// have to call 'super()' first because it needs to initialize 'someProperty'.doSomeStuff();super();}}
這使得檢查 super() 在引用 this 之前被呼叫變得很簡單,但它最終拒絕了許多有效的程式碼。TypeScript 4.6 現在對該檢查更加寬鬆,允許在 super() 之前執行其他程式碼,同時仍然確保 super() 出現在任何引用 this 之前的頂層位置。
我們要感謝 Joshua Goldberg 耐心與我們合作完成此項更改!
解構判別式聯合型別的控制流分析
TypeScript 能夠根據所謂的判別屬性來收窄型別。例如,在下面的程式碼片段中,TypeScript 能夠在我們每次檢查 kind 的值時收窄 action 的型別。
tstype Action =| { kind: "NumberContents"; payload: number }| { kind: "StringContents"; payload: string };function processAction(action: Action) {if (action.kind === "NumberContents") {// `action.payload` is a number here.let num = action.payload * 2;// ...} else if (action.kind === "StringContents") {// `action.payload` is a string here.const str = action.payload.trim();// ...}}
這使我們能夠處理可以儲存不同資料的物件,但一個公共欄位告訴我們這些物件擁有哪種資料。
這在 TypeScript 中非常常見;然而,根據您的偏好,您可能希望在上面的示例中解構 kind 和 payload。也許類似於以下內容
tstype Action =| { kind: "NumberContents"; payload: number }| { kind: "StringContents"; payload: string };function processAction(action: Action) {const { kind, payload } = action;if (kind === "NumberContents") {let num = payload * 2;// ...} else if (kind === "StringContents") {const str = payload.trim();// ...}}
以前 TypeScript 會在這些程式碼上報錯——一旦 kind 和 payload 從同一個物件中提取到變數中,它們就被視為完全獨立的。
在 TypeScript 4.6 中,這可以直接工作了!
當將單個屬性解構到 const 宣告中,或者將引數解構為從未被重新賦值的變數時,TypeScript 將檢查被解構的型別是否為判別式聯合型別。如果是,TypeScript 現在可以根據其他變數的檢查來收窄變數的型別。因此在我們的示例中,對 kind 的檢查會收窄 payload 的型別。
更多資訊,請參閱實現此分析的拉取請求。
改進的遞迴深度檢查
由於 TypeScript 是建立在同時提供泛型的結構化型別系統之上的,因此它面臨一些有趣的挑戰。
在結構化型別系統中,物件型別的相容性取決於它們擁有的成員。
tsinterface Source {prop: string;}interface Target {prop: number;}function check(source: Source, target: Target) {target = source;// error!// Type 'Source' is not assignable to type 'Target'.// Types of property 'prop' are incompatible.// Type 'string' is not assignable to type 'number'.}
請注意,Source 是否與 Target 相容與它們的屬性是否可賦值有關。在這種情況下,也就是 prop。
當您向其中引入泛型時,會有一些更難回答的問題。例如,在以下情況下,Source<string> 可賦值給 Target<number> 嗎?
tsinterface Source<T> {prop: Source<Source<T>>;}interface Target<T> {prop: Target<Target<T>>;}function check(source: Source<string>, target: Target<number>) {target = source;}
為了回答這個問題,TypeScript 需要檢查 prop 的型別是否相容。這導致了另一個問題:Source<Source<string>> 可賦值給 Target<Target<number>> 嗎?為了回答這個問題,TypeScript 會檢查 prop 對於那些型別是否相容,並最終檢查 Source<Source<Source<string>>> 是否可賦值給 Target<Target<Target<number>>>。繼續下去,您可能會注意到挖掘得越深,型別擴充套件得就越無限。
TypeScript 在這裡有一些啟發式方法——如果一個型別在遇到一定的深度檢查後看起來是無限擴充套件的,那麼它會認為這些型別可能是相容的。這通常就足夠了,但令人尷尬的是,有一些假陰性是這種方法無法捕捉到的。
tsinterface Foo<T> {prop: T;}declare let x: Foo<Foo<Foo<Foo<Foo<Foo<string>>>>>>;declare let y: Foo<Foo<Foo<Foo<Foo<string>>>>>;x = y;
人類讀者可以看到上面的例子中 x 和 y 應該是互不相容的。雖然型別巢狀很深,但這只是它們宣告方式的結果。這種啟發式方法旨在捕捉那些透過探索型別而生成的深層巢狀型別的情況,而不是開發人員自己編寫的那種。
TypeScript 4.6 現在能夠區分這些情況,並正確地對最後一個示例報錯。此外,由於語言不再擔心顯式編寫的型別帶來的誤報,TypeScript 可以更早地斷定一個型別是無限擴充套件的,從而在檢查型別相容性時節省大量工作。結果是,DefinitelyTyped 上的 redux-immutable、react-lazylog 和 yup 等庫的檢查時間減少了 50%。
您可能已經擁有此更改,因為它被 cherry-picked 到了 TypeScript 4.5.3 中,但它是 TypeScript 4.6 的一個顯著特性,您可以在此處閱讀更多相關資訊。
索引訪問型別推斷改進
TypeScript 現在可以正確推斷出直接索引到對映物件型別的索引訪問型別。
tsinterface TypeMap {number: number;string: string;boolean: boolean;}type UnionRecord<P extends keyof TypeMap> = {[K in P]: {kind: K;v: TypeMap[K];f: (p: TypeMap[K]) => void;};}[P];function processRecord<K extends keyof TypeMap>(record: UnionRecord<K>) {record.f(record.v);}// This call used to have issues - now works!processRecord({kind: "string",v: "hello!",// 'val' used to implicitly have the type 'string | number | boolean',// but now is correctly inferred to just 'string'.f: (val) => {console.log(val.toUpperCase());},});
這種模式早已得到支援,並使 TypeScript 能夠理解對 record.f(record.v) 的呼叫是有效的,但之前對 processRecord 的呼叫會導致 val 的推斷結果很差。
TypeScript 4.6 改進了這一點,因此在呼叫 processRecord 時不再需要型別斷言。
更多資訊,您可以閱讀拉取請求。
相關引數的控制流分析
可以宣告一個具有剩餘引數(rest parameter)的簽名,其型別是元組的判別式聯合型別。
tsfunction func(...args: ["str", string] | ["num", number]) {// ...}
這表示 func 的引數完全取決於第一個引數。當第一個引數是字串 "str" 時,第二個引數必須是 string。當第一個引數是字串 "num" 時,第二個引數必須是 number。
在 TypeScript 從這樣的簽名中推斷函式型別的情況下,TypeScript 現在可以收窄相互依賴的引數。
tstype Func = (...args: ["a", number] | ["b", string]) => void;const f1: Func = (kind, payload) => {if (kind === "a") {payload.toFixed(); // 'payload' narrowed to 'number'}if (kind === "b") {payload.toUpperCase(); // 'payload' narrowed to 'string'}};f1("a", 42);f1("b", "hello");
更多資訊,請參閱 GitHub 上的更改。
--target es2022
TypeScript 的 --target 選項現在支援 es2022。這意味著類欄位等特性現在有了穩定的輸出目標,可以被保留下來。這也意味著可以使用新的內建功能,例如 Array 的 at() 方法、Object.hasOwn 或 new Error 上的 cause 選項,既可以透過這個新的 --target 設定使用,也可以透過 --lib es2022 使用。
此功能由 Kagami Sascha Rosylight (saschanaz) 透過多個 PR 實現,我們非常感謝這一貢獻!
在 react-jsx 中移除不必要的引數
此前,在 --jsx react-jsx 下編譯如下程式碼時
tsxexport const el = <div>foo</div>;
TypeScript 會生成以下 JavaScript 程式碼
jsximport { jsx as _jsx } from "react/jsx-runtime";export const el = _jsx("div", { children: "foo" }, void 0);
最後一個 void 0 引數在此編譯模式下是不必要的,刪除它可以減小包體積。
diff- export const el = _jsx("div", { children: "foo" }, void 0);+ export const el = _jsx("div", { children: "foo" });
感謝來自 Alexander Tarasyuk 的拉取請求,TypeScript 4.6 現在刪除了 void 0 引數。
JSDoc 名稱建議
在 JSDoc 中,您可以使用 @param 標籤記錄引數。
js/*** @param x The first operand* @param y The second operand*/function add(x, y) {return x + y;}
但是當這些註釋過期時會發生什麼呢?如果我們把 x 和 y 重新命名為 a 和 b 會怎樣?
js/*** @param x {number} The first operand* @param y {number} The second operand*/function add(a, b) {return a + b;}
此前,TypeScript 僅在對 JavaScript 檔案執行型別檢查時(即使用 checkJs 選項,或在檔案頂部新增 // @ts-check 註釋時)才會提醒您這一點。
現在,您可以在編輯器中獲得 TypeScript 檔案的類似資訊!當引數名稱在函式與其 JSDoc 註釋之間不匹配時,TypeScript 現在會提供建議。

此更改由 Alexander Tarasyuk 提供!
JavaScript 中的更多語法和繫結錯誤
TypeScript 擴充套件了其在 JavaScript 檔案中的語法和繫結錯誤集合。如果您在 Visual Studio 或 Visual Studio Code 等編輯器中開啟 JavaScript 檔案,或者透過 TypeScript 編譯器執行 JavaScript 程式碼(即使您沒有開啟 checkJs 或在檔案頂部新增 // @ts-check 註釋),您也會看到這些新錯誤。
舉個例子,如果您在 JavaScript 檔案的同一作用域中有兩個 const 的宣告,TypeScript 現在會對這些聲明發出錯誤。
tsconst foo = 1234;// ~~~// error: Cannot redeclare block-scoped variable 'foo'.// ...const foo = 5678;// ~~~// error: Cannot redeclare block-scoped variable 'foo'.
再舉一個例子,如果您錯誤地使用了修飾符,TypeScript 會告知您。
tsfunction container() {export function foo() {// ~~~~~~// error: Modifiers cannot appear here.}}
可以透過在檔案頂部新增 // @ts-nocheck 來停用這些錯誤,但我們很有興趣聽取一些關於它如何影響您的 JavaScript 工作流程的早期反饋。您可以透過安裝 TypeScript and JavaScript Nightly 擴充套件輕鬆在 Visual Studio Code 中嘗試它,並閱讀第一個和第二個拉取請求以瞭解更多資訊。
TypeScript 跟蹤分析器
偶爾,團隊可能會遇到建立和與其他型別進行比較時計算成本高昂的型別。TypeScript 有一個 --generateTrace 標誌來幫助識別其中一些昂貴的型別,或者有時幫助診斷 TypeScript 編譯器中的問題。雖然 --generateTrace 生成的資訊可能很有用(特別是 TypeScript 4.6 中新增的一些資訊),但它在現有的跟蹤視覺化工具中通常難以閱讀。
我們最近釋出了一個名為 @typescript/analyze-trace 的工具,可以更直觀地檢視此資訊。雖然我們不指望每個人都需要 analyze-trace,但我們認為對於任何遇到 TypeScript 構建效能問題的團隊來說,它都能派上用場。
更多資訊,請參閱 analyze-trace 工具的倉庫。
破壞性變更
物件剩餘引數從泛型物件中剔除不可展開的成員
物件剩餘表示式現在剔除泛型物件上看起來不可展開的成員。在下面的示例中……
tsclass Thing {someProperty = 42;someMethod() {// ...}}function foo<T extends Thing>(x: T) {let { someProperty, ...rest } = x;// Used to work, is now an error!// Property 'someMethod' does not exist on type 'Omit<T, "someProperty" | "someMethod">'.rest.someMethod();}
變數 rest 曾經具有 Omit<T, "someProperty"> 型別,因為 TypeScript 會嚴格分析哪些其他屬性被解構了。這並沒有模擬出 ...rest 在非泛型型別解構中是如何工作的,因為 someMethod 通常也會被剔除。在 TypeScript 4.6 中,rest 的型別是 Omit<T, "someProperty" | "someMethod">。
這種情況也會出現在從 this 解構時。當使用 ...rest 元素解構 this 時,不可展開和非公共成員現在會被剔除,這與在其他地方解構類例項是一致的。
tsclass Thing {someProperty = 42;someMethod() {// ...}someOtherMethod() {let { someProperty, ...rest } = this;// Used to work, is now an error!// Property 'someMethod' does not exist on type 'Omit<T, "someProperty" | "someMethod">'.rest.someMethod();}}
更多細節,請參閱此處的相應更改。
JavaScript 檔案始終接收語法和繫結錯誤
此前,TypeScript 除了忽略在 JavaScript 檔案中意外使用 TypeScript 語法的情況外,會忽略大多數 JavaScript 中的語法錯誤。TypeScript 現在會在您的檔案中顯示 JavaScript 語法和繫結錯誤,例如使用不正確的修飾符、重複宣告等。這些通常在 Visual Studio Code 或 Visual Studio 中最為明顯,但也可能在透過 TypeScript 編譯器執行 JavaScript 程式碼時出現。
您可以透過在檔案頂部插入 // @ts-nocheck 註釋來明確關閉這些錯誤。