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 日