TypeScript 5.1

為返回 undefined 的函式簡化隱式返回

在 JavaScript 中,如果函式執行結束而沒有遇到 return 語句,它會返回 undefined 值。

ts
function foo() {
// no return
}
// x = undefined
let x = foo();

然而,在舊版本的 TypeScript 中,唯一可以完全沒有 return 語句的函式是返回 voidany 的函式。這意味著即使你顯式地宣告“此函式返回 undefined”,你也必須至少編寫一個 return 語句。

ts
// ✅ fine - we inferred that 'f1' returns 'void'
function f1() {
// no returns
}
// ✅ fine - 'void' doesn't need a return statement
function f2(): void {
// no returns
}
// ✅ fine - 'any' doesn't need a return statement
function f3(): any {
// no returns
}
// ❌ error!
// A function whose declared type is neither 'void' nor 'any' must return a value.
function f4(): undefined {
// no returns
}

如果某個 API 要求函式返回 undefined,這可能會很麻煩——你必須至少顯式返回一次 undefined,或者包含一個 return 語句並且進行顯式型別註解。

ts
declare function takesFunction(f: () => undefined): undefined;
// ❌ error!
// Argument of type '() => void' is not assignable to parameter of type '() => undefined'.
takesFunction(() => {
// no returns
});
// ❌ error!
// A function whose declared type is neither 'void' nor 'any' must return a value.
takesFunction((): undefined => {
// no returns
});
// ❌ error!
// Argument of type '() => void' is not assignable to parameter of type '() => undefined'.
takesFunction(() => {
return;
});
// ✅ works
takesFunction(() => {
return undefined;
});
// ✅ works
takesFunction((): undefined => {
return;
});

這種行為令人沮喪且困惑,特別是在呼叫不受自己控制的函式時。理解在 voidundefined 推斷之間、返回 undefined 的函式是否需要 return 語句等問題之間的相互作用,似乎是一種干擾。

首先,TypeScript 5.1 現在允許返回 undefined 的函式無需 return 語句。

ts
// ✅ Works in TypeScript 5.1!
function f4(): undefined {
// no returns
}
// ✅ Works in TypeScript 5.1!
takesFunction((): undefined => {
// no returns
});

其次,如果一個函式沒有返回表示式,並且被傳遞給期望返回 undefined 的函式的地方,TypeScript 會為該函式的返回型別推斷為 undefined

ts
// ✅ Works in TypeScript 5.1!
takesFunction(function f() {
// ^ return type is undefined
// no returns
});
// ✅ Works in TypeScript 5.1!
takesFunction(function f() {
// ^ return type is undefined
return;
});

為了解決另一個類似的問題,在 TypeScript 的 --noImplicitReturns 選項下,現在 返回 undefined 的函式與 void 函式享有類似的豁免權,即不需要每一條程式碼路徑都以顯式的 return 結尾。

ts
// ✅ Works in TypeScript 5.1 under '--noImplicitReturns'!
function f(): undefined {
if (Math.random()) {
// do some stuff...
return;
}
}

有關更多資訊,您可以閱讀原始議題實現該功能的 PR

Getter 和 Setter 的不相關型別

TypeScript 4.3 允許 getset 訪問器對指定兩種不同的型別。

ts
interface Serializer {
set value(v: string | number | boolean);
get value(): string;
}
declare let box: Serializer;
// Allows writing a 'boolean'
box.value = true;
// Comes out as a 'string'
console.log(box.value.toUpperCase());

最初,我們要求 get 的型別必須是 set 型別的子型別。這意味著編寫:

ts
box.value = box.value;

將始終是有效的。

然而,許多現有的和提議的 API 在其 getter 和 setter 之間具有完全不相關的型別。例如,考慮最常見的例子之一——DOM 中的 style 屬性和 CSSStyleRule API。每個樣式規則都有一個 style 屬性,它是一個 CSSStyleDeclaration;但是,如果你嘗試寫入該屬性,它僅在傳入字串時才能正常工作!

TypeScript 5.1 現在允許 getset 訪問器屬性擁有完全不相關的型別,前提是它們有顯式的型別註解。雖然此版本的 TypeScript 尚未更改這些內建介面的型別,但 CSSStyleRule 現在可以按以下方式定義:

ts
interface CSSStyleRule {
// ...
/** Always reads as a `CSSStyleDeclaration` */
get style(): CSSStyleDeclaration;
/** Can only write a `string` here. */
set style(newValue: string);
// ...
}

這也允許其他模式,例如要求 set 訪問器僅接受“有效”資料,但指定如果某些底層狀態尚未初始化,get 訪問器可以返回 undefined

ts
class SafeBox {
#value: string | undefined;
// Only accepts strings!
set value(newValue: string) {
}
// Must check for 'undefined'!
get value(): string | undefined {
return this.#value;
}
}

事實上,這類似於在 --exactOptionalProperties 下檢查可選屬性的方式。

您可以閱讀更多關於實現該功能的 PR 的資訊。

解耦 JSX 元素與 JSX 標籤型別的型別檢查

TypeScript 在 JSX 方面的一個痛點是它對每個 JSX 元素標籤型別的要求。

作為背景,JSX 元素通常是以下之一:

tsx
// A self-closing JSX tag
<Foo />
// A regular element with an opening/closing tag
<Bar></Bar>

當型別檢查 <Foo /><Bar></Bar> 時,TypeScript 總是查詢名為 JSX 的名稱空間並從中獲取名為 Element 的型別——或者更直接地說,它查詢 JSX.Element

但是,為了檢查 FooBar 本身是否可以用作標籤名稱,TypeScript 大致上會獲取由 FooBar 返回或構造的型別,並檢查其與 JSX.Element(如果型別是可構造的,則為 JSX.ElementClass)的相容性。

這裡的限制意味著,如果元件返回或“渲染”的型別比 JSX.Element 更廣泛,則無法使用它們。例如,JSX 庫可能允許元件返回 stringPromise

作為一個更具體的例子,React 正在考慮增加對返回 Promise 的元件的有限支援,但現有的 TypeScript 版本無法在不大幅放寬 JSX.Element 型別的情況下表達這一點。

tsx
import * as React from "react";
async function Foo() {
return <div></div>;
}
let element = <Foo />;
// ~~~
// 'Foo' cannot be used as a JSX component.
// Its return type 'Promise<Element>' is not a valid JSX element.

為了提供給庫一種表達方式,TypeScript 5.1 現在查詢名為 JSX.ElementType 的型別。ElementType 準確指定了什麼可以用作 JSX 元素中的標籤。因此,它今天可能被定義為類似:

tsx
namespace JSX {
export type ElementType =
// All the valid lowercase tags
keyof IntrinsicAttributes
// Function components
(props: any) => Element
// Class components
new (props: any) => ElementClass;
export interface IntrinsicAttributes extends /*...*/ {}
export type Element = /*...*/;
export type ElementClass = /*...*/;
}

我們要感謝 Sebastian Silbermann,他貢獻了這一更改

名稱空間的 JSX 屬性

TypeScript 現在在 JSX 中支援名稱空間的屬性名稱。

tsx
import * as React from "react";
// Both of these are equivalent:
const x = <Foo a:b="hello" />;
const y = <Foo a : b="hello" />;
interface FooProps {
"a:b": string;
}
function Foo(props: FooProps) {
return <div>{props["a:b"]}</div>;
}

當名稱的第一部分是小寫名稱時,會在 JSX.IntrinsicAttributes 上以類似方式查詢名稱空間標籤名稱。

tsx
// In some library's code or in an augmentation of that library:
namespace JSX {
interface IntrinsicElements {
["a:b"]: { prop: string };
}
}
// In our code:
let x = <a:b prop="hello!" />;

這一貢獻Oleksandr Tarasiuk 提供。

模組解析中會查閱 typeRoots

當 TypeScript 的指定模組查詢策略無法解析路徑時,現在將相對於指定的 typeRoots 解析包。

檢視此 PR 以獲取更多詳細資訊。

將宣告移動到現有檔案

除了將宣告移動到新檔案外,TypeScript 現在還發布了一項預覽功能,用於將宣告移動到現有檔案。您可以在 Visual Studio Code 的最新版本中嘗試此功能。

Moving a function 'getThanks' to an existing file in the workspace.

請記住,此功能目前處於預覽階段,我們正在徵求進一步的反饋。

https://github.com/microsoft/TypeScript/pull/53542

JSX 標籤的連結游標

TypeScript 現在支援 JSX 標籤名稱的連結編輯(Linked Editing)。連結編輯(有時稱為“映象游標”)允許編輯器同時自動編輯多個位置。

An example of JSX tags with linked editing modifying a JSX fragment and a div element.

此新功能應在 TypeScript 和 JavaScript 檔案中均可工作,並可在 Visual Studio Code Insiders 中啟用。在 Visual Studio Code 中,您可以在設定 UI 中編輯 Editor: Linked Editing 選項,

Visual Studio Code's Editor: Linked Editing` option

或者在 JSON 設定檔案中配置 editor.linkedEditing

jsonc
{
// ...
"editor.linkedEditing": true,
}

此功能也將受到 Visual Studio 17.7 Preview 1 的支援。

您可以檢視我們對連結編輯的實現

@param JSDoc 標籤的程式碼片段補全

TypeScript 現在在 TypeScript 和 JavaScript 檔案中鍵入 @param 標籤時提供程式碼片段補全。這有助於減少您在記錄程式碼或在 JavaScript 中新增 JSDoc 型別時的鍵入和游標跳轉次數。

An example of completing JSDoc param comments on an 'add' function.

您可以在 GitHub 上檢視此新功能的實現方式

最佳化

避免不必要的型別例項化

TypeScript 5.1 現在避免在已知不包含對外部型別引數引用的物件型別內執行型別例項化。這有潛力減少許多不必要的計算,並將 material-ui 的 docs 目錄的型別檢查時間縮短了 50% 以上。

您可以在 GitHub 上檢視此更改所涉及的內容

聯合字面量的否定情況檢查

在檢查源型別是否為聯合型別的一部分時,TypeScript 首先使用該源的內部型別識別符號進行快速查詢。如果該查詢失敗,TypeScript 將檢查與聯合中每種型別的相容性。

當將字面量型別關聯到純字面量型別的聯合時,TypeScript 現在可以避免與聯合中所有其他型別進行完整的遍歷。這種假設是安全的,因為 TypeScript 總是快取字面量型別——儘管在處理“新鮮”字面量型別時有一些邊緣情況需要處理。

這一最佳化成功地將 該問題中的程式碼的型別檢查時間從約 45 秒降低到了約 0.4 秒。

減少 JSDoc 解析對掃描器的呼叫

舊版本的 TypeScript 在解析 JSDoc 註釋時,會使用掃描器/分詞器將註釋分解為細粒度的標記,然後再將其內容重新組合在一起。這有助於規範化註釋文字,使多個空格摺疊為一個;但它非常“囉嗦”,意味著解析器和掃描器必須非常頻繁地來回跳轉,增加了 JSDoc 解析的開銷。

TypeScript 5.1 將更多將 JSDoc 註釋分解的邏輯移到了掃描器/分詞器中。掃描器現在直接將大塊內容返回給解析器,由解析器按需處理。

這些更改使幾個 10Mb 主要為散文註釋的 JavaScript 檔案的解析時間減少了約一半。對於一個更現實的例子,我們效能套件中 xstate 的快照解析時間減少了約 300ms,從而加快了載入和分析速度。

破壞性變更

ES2020 和 Node.js 14.17 作為最低執行時要求

TypeScript 5.1 現在釋出了 ECMAScript 2020 中引入的 JavaScript 功能。因此,TypeScript 最低必須在相當現代的執行時中執行。對於大多數使用者而言,這意味著 TypeScript 現在僅在 Node.js 14.17 及更高版本上執行。

如果您嘗試在較舊版本的 Node.js(如 Node 10 或 12)下執行 TypeScript 5.1,則在執行 tsc.jstsserver.js 時,您可能會看到類似以下的錯誤:

node_modules/typescript/lib/tsserver.js:2406
for (let i = startIndex ?? 0; i < array.length; i++) {
^
SyntaxError: Unexpected token '?'
at wrapSafe (internal/modules/cjs/loader.js:915:16)
at Module._compile (internal/modules/cjs/loader.js:963:27)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10)
at Module.load (internal/modules/cjs/loader.js:863:32)
at Function.Module._load (internal/modules/cjs/loader.js:708:14)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:60:12)
at internal/main/run_main_module.js:17:47

此外,如果您嘗試安裝 TypeScript,您會從 npm 收到類似以下的錯誤訊息:

npm WARN EBADENGINE Unsupported engine {
npm WARN EBADENGINE package: 'typescript@5.1.1-rc',
npm WARN EBADENGINE required: { node: '>=14.17' },
npm WARN EBADENGINE current: { node: 'v12.22.12', npm: '8.19.2' }
npm WARN EBADENGINE }

來自 Yarn:

error typescript@5.1.1-rc: The engine "node" is incompatible with this module. Expected version ">=14.17". Got "12.22.12"
error Found incompatible module.

在此處檢視有關此更改的更多資訊:.

顯式的 typeRoots 停用 node_modules/@types 的向上遍歷

以前,當在 tsconfig.json 中指定了 typeRoots 選項但未能解析到任何 typeRoots 目錄時,TypeScript 仍會繼續向上遍歷父目錄,嘗試在每個父級的 node_modules/@types 資料夾中解析包。

這種行為可能會導致過多的查詢,並且已在 TypeScript 5.1 中被停用。因此,您可能會根據 tsconfig.jsontypes 選項或 /// <reference > 指令中的條目開始看到類似以下的錯誤:

error TS2688: Cannot find type definition file for 'node'.
error TS2688: Cannot find type definition file for 'mocha'.
error TS2688: Cannot find type definition file for 'jasmine'.
error TS2688: Cannot find type definition file for 'chai-http'.
error TS2688: Cannot find type definition file for 'webpack-env"'.

解決方法通常是將 node_modules/@types 的特定條目新增到您的 typeRoots 中:

jsonc
{
"compilerOptions": {
"types": [
"node",
"mocha"
],
"typeRoots": [
// Keep whatever you had around before.
"./some-custom-types/",
// You might need your local 'node_modules/@types'.
"./node_modules/@types",
// You might also need to specify a shared 'node_modules/@types'
// if you're using a "monorepo" layout.
"../../node_modules/@types",
]
}
}

有關更多資訊,請參閱我們議題跟蹤器上的原始更改

TypeScript 文件是一個開源專案。透過傳送 Pull Request 幫助我們改進這些頁面 ❤

此頁面的貢獻者
Nnavya9singh (6)
BTBeeno Tung (1)
LLLazar Ljubenović (1)

最後更新:2026 年 3 月 27 日