TypeScript 5.4

在最后一次赋值后保留闭包中的收缩

¥Preserved Narrowing in Closures Following Last Assignments

TypeScript 通常可以根据你可能执行的检查为变量找出更具体的类型。此过程称为缩小范围。

¥TypeScript can usually figure out a more specific type for a variable based on checks that you might perform. This process is called narrowing.

ts
function uppercaseStrings(x: string | number) {
if (typeof x === "string") {
// TypeScript knows 'x' is a 'string' here.
return x.toUpperCase();
}
}

一个常见的痛点是,这些缩小的类型并不总是在函数闭包中保留。

¥One common pain point was that these narrowed types weren’t always preserved within function closures.

ts
function getUrls(url: string | URL, names: string[]) {
if (typeof url === "string") {
url = new URL(url);
}
return names.map(name => {
url.searchParams.set("name", name)
// ~~~~~~~~~~~~
// error!
// Property 'searchParams' does not exist on type 'string | URL'.
return url.toString();
});
}

这里,TypeScript 认为在我们的回调函数中将 url 假设为 URL 对象并非 “safe”,因为它在其他地方被修改了;但是,在这种情况下,箭头函数总是在对 url 赋值之后创建,并且这也是对 url 的最后一次赋值。

¥Here, TypeScript decided that it wasn’t “safe” to assume that url was actually a URL object in our callback function because it was mutated elsewhere; however, in this instance, that arrow function is always created after that assignment to url, and it’s also the last assignment to url.

TypeScript 5.4 利用这一点,使缩小范围变得更加智能。当参数和 let 变量用于非 hoisted 函数时,类型检查器会查找最后一个赋值点。如果找到一个,TypeScript 可以安全地从包含函数的外部进行缩小。这意味着上面的例子现在可以正常工作了。

¥TypeScript 5.4 takes advantage of this to make narrowing a little smarter. When parameters and let variables are used in non-hoisted functions, the type-checker will look for a last assignment point. If one is found, TypeScript can safely narrow from outside the containing function. What that means is the above example just works now.

请注意,如果变量在嵌套函数中的任何位置赋值,则不会启动收缩分析。这是因为无法确定该函数是否会在稍后被调用。

¥Note that narrowing analysis doesn’t kick in if the variable is assigned anywhere in a nested function. This is because there’s no way to know for sure whether the function will be called later.

ts
function printValueLater(value: string | undefined) {
if (value === undefined) {
value = "missing!";
}
setTimeout(() => {
// Modifying 'value', even in a way that shouldn't affect
// its type, will invalidate type refinements in closures.
value = value;
}, 500);
setTimeout(() => {
console.log(value.toUpperCase());
// ~~~~~
// error! 'value' is possibly 'undefined'.
}, 1000);
}

这应该使许多典型的 JavaScript 代码更容易表达。你可以 在 GitHub 上阅读有关变更的更多信息

¥This should make lots of typical JavaScript code easier to express. You can read more about the change on GitHub.

NoInfer 工具类型

¥The NoInfer Utility Type

调用泛型函数时,TypeScript 能够根据传入的内容推断出类型参数。

¥When calling generic functions, TypeScript is able to infer type arguments from whatever you pass in.

ts
function doSomething<T>(arg: T) {
// ...
}
// We can explicitly say that 'T' should be 'string'.
doSomething<string>("hello!");
// We can also just let the type of 'T' get inferred.
doSomething("hello!");

然而,一个挑战是,“best” 类型推断并不总是很清楚。这可能会导致 TypeScript 拒绝有效的调用、接受可疑的调用,或者在捕获到错误时报告更糟糕的错误消息。

¥One challenge, however, is that it is not always clear what the “best” type is to infer. This might lead to TypeScript rejecting valid calls, accepting questionable calls, or just reporting worse error messages when it catches a bug.

例如,假设有一个 createStreetLight 函数,它接受一个颜色名称列表以及一个可选的默认颜色。

¥For example, let’s imagine a createStreetLight function that takes a list of color names, along with an optional default color.

ts
function createStreetLight<C extends string>(colors: C[], defaultColor?: C) {
// ...
}
createStreetLight(["red", "yellow", "green"], "red");

如果我们传入一个原始 colors 数组中不存在的 defaultColor,会发生什么?在此函数中,colors 应该是 “真相来源”,并描述了可以传递给 defaultColor 的内容。

¥What happens when we pass in a defaultColor that wasn’t in the original colors array? In this function, colors is supposed to be the “source of truth” and describe what can be passed to defaultColor.

ts
// Oops! This is undesirable, but is allowed!
createStreetLight(["red", "yellow", "green"], "blue");

在此调用中,类型推断确定 "blue" 的类型与 "red""yellow""green" 一样有效。因此,TypeScript 不会拒绝调用,而是将 C 的类型推断为 "red" | "yellow" | "green" | "blue"。你可能会说,这种推论简直令人难以置信!

¥In this call, type inference decided that "blue" was just as valid of a type as "red" or "yellow" or "green". So instead of rejecting the call, TypeScript infers the type of C as "red" | "yellow" | "green" | "blue". You might say that inference just blue up in our faces!

目前人们处理此问题的一种方法是添加一个单独的类型参数,该参数受现有类型参数的限制。

¥One way people currently deal with this is to add a separate type parameter that’s bounded by the existing type parameter.

ts
function createStreetLight<C extends string, D extends C>(colors: C[], defaultColor?: D) {
}
createStreetLight(["red", "yellow", "green"], "blue");
// ~~~~~~
// error!
// Argument of type '"blue"' is not assignable to parameter of type '"red" | "yellow" | "green" | undefined'.

这可行,但有点尴尬,因为 D 可能不会在 createStreetLight 签名的其他任何地方使用。虽然在这种情况下还不错,但在签名中仅使用一次类型参数通常会导致代码异味。

¥This works, but is a little bit awkward because D probably won’t be used anywhere else in the signature for createStreetLight. While not bad in this case, using a type parameter only once in a signature is often a code smell.

这就是为什么 TypeScript 5.4 引入了一个新的 NoInfer<T> 工具类型。用 NoInfer<...> 包围类型会向 TypeScript 触发信号,使其不要深入挖掘并与内部类型匹配以查找类型推断的候选对象。

¥That’s why TypeScript 5.4 introduces a new NoInfer<T> utility type. Surrounding a type in NoInfer<...> gives a signal to TypeScript not to dig in and match against the inner types to find candidates for type inference.

使用 NoInfer,我们可以将 createStreetLight 重写为如下形式:

¥Using NoInfer, we can rewrite createStreetLight as something like this:

ts
function createStreetLight<C extends string>(colors: C[], defaultColor?: NoInfer<C>) {
// ...
}
createStreetLight(["red", "yellow", "green"], "blue");
// ~~~~~~
// error!
// Argument of type '"blue"' is not assignable to parameter of type '"red" | "yellow" | "green" | undefined'.

defaultColor 的类型排除在推断探索之外意味着 "blue" 永远不会成为推断候选,类型检查器可以拒绝它。

¥Excluding the type of defaultColor from being explored for inference means that "blue" never ends up as an inference candidate, and the type-checker can reject it.

你可以查看 实现拉取请求 中的具体更改,以及感谢 Mateusz Burzyński 提供的 初始实现

¥You can see the specific changes in the implementing pull request, along with the initial implementation provided thanks to Mateusz Burzyński!

Object.groupByMap.groupBy

¥Object.groupBy and Map.groupBy

TypeScript 5.4 为 JavaScript 的新 Object.groupByMap.groupBy 静态方法添加了声明。

¥TypeScript 5.4 adds declarations for JavaScript’s new Object.groupBy and Map.groupBy static methods.

Object.groupBy 接受一个可迭代对象和一个函数,该函数决定每个元素应放置在哪个 “group” 中。该函数需要为每个不同的组创建一个 “key”,并且 Object.groupBy 使用该键创建一个对象,其中每个键都映射到一个包含原始元素的数组。

¥Object.groupBy takes an iterable, and a function that decides which “group” each element should be placed in. The function needs to make a “key” for each distinct group, and Object.groupBy uses that key to make an object where every key maps to an array with the original element in it.

因此,以下 JavaScript 代码:

¥So the following JavaScript:

js
const array = [0, 1, 2, 3, 4, 5];
const myObj = Object.groupBy(array, (num, index) => {
return num % 2 === 0 ? "even": "odd";
});

基本上等同于这样写:

¥is basically equivalent to writing this:

js
const myObj = {
even: [0, 2, 4],
odd: [1, 3, 5],
};

Map.groupBy 类似,但生成的是 Map 而不是普通对象。如果你需要 Map 的保证、处理的 API 需要 Map,或者你需要使用任何类型的键进行分组,那么这可能更可取。 - 不仅仅是 JavaScript 中可用作属性名的键。

¥Map.groupBy is similar, but produces a Map instead of a plain object. This might be more desirable if you need the guarantees of Maps, you’re dealing with APIs that expect Maps, or you need to use any kind of key for grouping - not just keys that can be used as property names in JavaScript.

js
const myObj = Map.groupBy(array, (num, index) => {
return num % 2 === 0 ? "even" : "odd";
});

和之前一样,你可以用等效的方式创建 myObj

¥and just as before, you could have created myObj in an equivalent way:

js
const myObj = new Map();
myObj.set("even", [0, 2, 4]);
myObj.set("odd", [1, 3, 5]);

请注意,在上面的 Object.groupBy 示例中,生成的对象使用了所有可选属性。

¥Note that in the above example of Object.groupBy, the object produced uses all optional properties.

ts
interface EvenOdds {
even?: number[];
odd?: number[];
}
const myObj: EvenOdds = Object.groupBy(...);
myObj.even;
// ~~~~
// Error to access this under 'strictNullChecks'.

这是因为无法以一般方式保证所有键都是由 groupBy 生成的。

¥This is because there’s no way to guarantee in a general way that all the keys were produced by groupBy.

另请注意,这些方法只能通过将 target 配置为 esnext 或调整 lib 设置来访问。我们预计它们最终将在稳定的 es2024 目标下可用。

¥Note also that these methods are only accessible by configuring your target to esnext or adjusting your lib settings. We expect they will eventually be available under a stable es2024 target.

我们要感谢 Kevin Gibbons 提供的 为这些 groupBy 方法添加声明

¥We’d like to extend a thanks to Kevin Gibbons for adding the declarations to these groupBy methods.

支持在 --moduleResolution bundler--module preserve 中调用 require()

¥Support for require() calls in --moduleResolution bundler and --module preserve

TypeScript 有一个名为 bundlermoduleResolution 选项,旨在模拟现代打包工具确定导入路径指向哪个文件的方式。该选项的限制之一是它必须与 --module esnext 搭配使用,因此无法使用 import ... = require(...) 语法。

¥TypeScript has a moduleResolution option called bundler that is meant to model the way modern bundlers figure out which file an import path refers to. One of the limitations of the option is that it had to be paired with --module esnext, making it impossible to use the import ... = require(...) syntax.

ts
// previously errored
import myModule = require("module/path");

如果你只打算编写标准 ECMAScript import,这似乎不是什么大问题,但在使用包含 条件导出 的包时会有所不同。

¥That might not seem like a big deal if you’re planning on just writing standard ECMAScript imports, but there’s a difference when using a package with conditional exports.

在 TypeScript 5.4 中,现在将 module 设置设置为名为 preserve 的新选项时可以使用 require()

¥In TypeScript 5.4, require() can now be used when setting the module setting to a new option called preserve.

--module preserve--moduleResolution bundler 之间,两者更准确地模拟了像 Bun 这样的打包器和运行时允许的操作,以及它们将如何执行模块查找。事实上,使用 --module preserve 时,bundler 选项将隐式设置为 --moduleResolution(以及 --esModuleInterop--resolveJsonModule)。

¥Between --module preserve and --moduleResolution bundler, the two more accurately model what bundlers and runtimes like Bun will allow, and how they’ll perform module lookups. In fact, when using --module preserve, the bundler option will be implicitly set for --moduleResolution (along with --esModuleInterop and --resolveJsonModule)

json
{
"compilerOptions": {
"module": "preserve",
// ^ also implies:
// "moduleResolution": "bundler",
// "esModuleInterop": true,
// "resolveJsonModule": true,
// ...
}
}

--module preserve 下,ECMAScript import 将始终按原样触发,而 import ... = require(...) 将作为 require() 调用触发(尽管实际上你甚至可能不会使用 TypeScript 来触发,因为你很可能会使用代码打包器)。无论包含文件的文件扩展名是什么,这都适用。所以这段代码的输出是:

¥Under --module preserve, an ECMAScript import will always be emitted as-is, and import ... = require(...) will be emitted as a require() call (though in practice you may not even use TypeScript for emit, since it’s likely you’ll be using a bundler for your code). This holds true regardless of the file extension of the containing file. So the output of this code:

ts
import * as foo from "some-package/foo";
import bar = require("some-package/bar");

应该看起来像这样:

¥should look something like this:

js
import * as foo from "some-package/foo";
var bar = require("some-package/bar");

这也意味着你选择的语法决定了 条件导出 的匹配方式。因此在上面的例子中,如果 some-packagepackage.json 如下所示:

¥What this also means is that the syntax you choose directs how conditional exports are matched. So in the above example, if the package.json of some-package looks like this:

json
{
"name": "some-package",
"version": "0.0.1",
"exports": {
"./foo": {
"import": "./esm/foo-from-import.mjs",
"require": "./cjs/foo-from-require.cjs"
},
"./bar": {
"import": "./esm/bar-from-import.mjs",
"require": "./cjs/bar-from-require.cjs"
}
}
}

TypeScript 将把这些路径解析为 [...]/some-package/esm/foo-from-import.mjs[...]/some-package/cjs/bar-from-require.cjs

¥TypeScript will resolve these paths to [...]/some-package/esm/foo-from-import.mjs and [...]/some-package/cjs/bar-from-require.cjs.

更多信息,你可以查看 在此处阅读这些新设置信息

¥For more information, you can read up on these new settings here.

已检查的导入属性和断言

¥Checked Import Attributes and Assertions

现在会根据全局 ImportAttributes 类型检查导入属性和断言。这意味着运行时现在可以更准确地描述导入属性

¥Import attributes and assertions are now checked against the global ImportAttributes type. This means that runtimes can now more accurately describe the import attributes

ts
// In some global file.
interface ImportAttributes {
type: "json";
}
// In some other module
import * as ns from "foo" with { type: "not-json" };
// ~~~~~~~~~~
// error!
//
// Type '{ type: "not-json"; }' is not assignable to type 'ImportAttributes'.
// Types of property 'type' are incompatible.
// Type '"not-json"' is not assignable to type '"json"'.

此变更Oleksandr Tarasiuk 提供!

¥This change was provided thanks to Oleksandr Tarasiuk.

快速修复添加缺失参数的问题

¥Quick Fix for Adding Missing Parameters

TypeScript 现在提供了一个快速修复方法,可以为使用过多参数调用的函数添加新形参。

¥TypeScript now has a quick fix to add a new parameter to functions that are called with too many arguments.

A quick fix being offered when someFunction calls someHelperFunction with 2 more arguments than are expected.

The missing arguments have been added to someHelperFunction after the quick fix was applied.

当将新参数传递到多个现有函数时,这很有用,而这在当今可能很麻烦。

¥This can be useful when threading a new argument through several existing functions, which can be cumbersome today.

此快速修复Oleksandr Tarasiuk 提供。

¥This quick fix was provided courtsey of Oleksandr Tarasiuk.

TypeScript 5.0 弃用功能即将带来的变更

¥Upcoming Changes from TypeScript 5.0 Deprecations

TypeScript 5.0 弃用了以下选项和行为:

¥TypeScript 5.0 deprecated the following options and behaviors:

  • charset

  • target: ES3

  • importsNotUsedAsValues

  • noImplicitUseStrict

  • noStrictGenericChecks

  • keyofStringsOnly

  • suppressExcessPropertyErrors

  • suppressImplicitAnyIndexErrors

  • out

  • preserveValueImports

  • 项目引用中的 prepend

    ¥prepend in project references

  • 隐式特定于操作系统的 newLine

    ¥implicitly OS-specific newLine

为了继续使用它们,使用 TypeScript 5.0 及其他较新版本的开发者必须指定一个名为 ignoreDeprecations 且值为 "5.0" 的新选项。

¥To continue using them, developers using TypeScript 5.0 and other more recent versions have had to specify a new option called ignoreDeprecations with the value "5.0".

然而,TypScript 5.4 将是这些功能能够继续正常运行的最后一个版本。到 TypeScript 5.5(可能在 2024 年 6 月发布)时,这些将成为硬错误,使用它们的代码将需要迁移。

¥However, TypScript 5.4 will be the last version in which these will continue to function as normal. By TypeScript 5.5 (likely June 2024), these will become hard errors, and code using them will need to be migrated away.

更多信息,你可以查看 在 GitHub 上阅读此计划信息,其中包含有关如何最佳地调整代码库的建议。

¥For more information, you can read up on this plan on GitHub, which contains suggestions in how to best adapt your codebase.

值得注意的行为变更

¥Notable Behavioral Changes

本节重点介绍了一系列值得注意的变更,在任何升级过程中都应确认并理解这些变更。有时它会高亮弃用、移除和新的限制。它还可以包含功能改进的错误修复,但这也可能通过引入新错误来影响现有构建。

¥This section highlights a set of noteworthy changes that should be acknowledged and understood as part of any upgrade. Sometimes it will highlight deprecations, removals, and new restrictions. It can also contain bug fixes that are functionally improvements, but which can also affect an existing build by introducing new errors.

lib.d.ts 变更

¥lib.d.ts Changes

为 DOM 生成的类型可能会对代码库的类型检查产生影响。更多信息请见 查看 TypeScript 5.4 的 DOM 更新

¥Types generated for the DOM may have an impact on type-checking your codebase. For more information, see the DOM updates for TypeScript 5.4.

更精确的条件类型约束

¥More Accurate Conditional Type Constraints

以下代码不再允许在函数 foo 中进行第二个变量声明。

¥The following code no longer allows the second variable declaration in the function foo.

ts
type IsArray<T> = T extends any[] ? true : false;
function foo<U extends object>(x: IsArray<U>) {
let first: true = x; // Error
let second: false = x; // Error, but previously wasn't
}

以前,当 TypeScript 检查 second 的初始化器时,它需要确定 IsArray<U> 是否可以赋值给单元类型 false。虽然 IsArray<U> 并不以任何明显的方式兼容,但 TypeScript 也会考虑该类型的约束。在像 T extends Foo ? TrueBranch : FalseBranch 这样的条件类型中,其中 T 是泛型,类型系统会查看 T 的约束,将其替换为 T 本身,然后决定是 true 还是 false 分支。

¥Previously, when TypeScript checked the initializer for second, it needed to determine whether IsArray<U> was assignable to the unit type false. While IsArray<U> isn’t compatible any obvious way, TypeScript looks at the constraint of that type as well. In a conditional type like T extends Foo ? TrueBranch : FalseBranch, where T is generic, the type system would look at the constraint of T, substitute it in for T itself, and decide on either the true or false branch.

但是这种行为并不准确,因为它过于急切。即使 T 的约束不能赋值给 Foo,也不意味着它不会被赋值给其他可以赋值的对象实例化。因此,更正确的做法是在无法证明 T 永远不会或总是扩展 Foo. 的情况下,为条件类型的约束生成一个联合类型。

¥But this behavior was inaccurate because it was overly eager. Even if the constraint of T isn’t assignable to Foo, that doesn’t mean that it won’t be instantiated with something that is. And so the more correct behavior is to produce a union type for the constraint of the conditional type in cases where it can’t be proven that T never or always extends Foo.

TypeScript 5.4 采用了这种更精确的行为。实际上,这意味着你可能会发现某些条件类型实例不再与其分支兼容。

¥TypeScript 5.4 adopts this more accurate behavior. What this means in practice is that you may begin to find that some conditional type instances are no longer compatible with their branches.

你可以在此处阅读有关具体更改的信息

¥You can read about the specific changes here.

更积极地减少类型变量和原始类型之间的交叉

¥More Aggressive Reduction of Intersections Between Type Variables and Primitive Types

TypeScript 现在会更积极地减少与类型变量和原语的交叉,具体取决于类型变量的约束与这些原语的重叠程度。

¥TypeScript now reduces intersections with type variables and primitives more aggressively, depending on how the type variable’s constraint overlaps with those primitives.

ts
declare function intersect<T, U>(x: T, y: U): T & U;
function foo<T extends "abc" | "def">(x: T, str: string, num: number) {
// Was 'T & string', now is just 'T'
let a = intersect(x, str);
// Was 'T & number', now is just 'never'
let b = intersect(x, num)
// Was '(T & "abc") | (T & "def")', now is just 'T'
let c = Math.random() < 0.5 ?
intersect(x, "abc") :
intersect(x, "def");
}

更多信息请见 查看此处的变更

¥For more information, see the change here.

改进了使用插值对模板字符串的检查

¥Improved Checking Against Template Strings with Interpolations

TypeScript 现在可以更准确地检查字符串是否可以分配给模板字符串类型的占位符槽。

¥TypeScript now more accurately checks whether or not strings are assignable to the placeholder slots of a template string type.

ts
function a<T extends {id: string}>() {
let x: `-${keyof T & string}`;
// Used to error, now doesn't.
x = "-id";
}

此行为是更理想的,但可能会导致使用条件类型等结构的代码中断,因为这些规则的更改很容易被察觉。

¥This behavior is more desirable, but may cause breaks in code using constructs like conditional types, where these rule changes are easy to witness.

查看更多详情,请参阅 查看此变更

¥See this change for more details.

仅类型导入与本地值冲突时的错误

¥Errors When Type-Only Imports Conflict with Local Values

以前,如果对 Something 的导入仅引用了类型。

¥Previously, TypeScript would permit the following code under isolatedModules if the import to Something only referred to a type.

ts
import { Something } from "./some/path";
let Something = 123;

但是,对于单文件编译器来说,即使代码肯定会在运行时失败,也不应该假设它是 “safe” 而删除 import。在 TypeScript 5.4 中,此代码将触发如下错误:

¥However, it’s not safe for single-file compilers to assume whether it’s “safe” to drop the import, even if the code is guaranteed to fail at runtime. In TypeScript 5.4, this code will trigger an error like the following:

Import 'Something' conflicts with local value, so must be declared with a type-only import when 'isolatedModules' is enabled.

修复方法是进行本地重命名,或者像错误提示那样,在导入中添加 type 修饰符:

¥The fix should be to either make a local rename, or, as the error states, add the type modifier to the import:

ts
import type { Something } from "./some/path";
// or
import { type Something } from "./some/path";

查看有关变更本身的更多信息

¥See more information on the change itself.

新的枚举可赋值性限制

¥New Enum Assignability Restrictions

当两个枚举具有相同的声明名称和枚举成员名称时,它们以前始终被视为兼容;但是,当值已知时,TypeScript 会默默地允许它们具有不同的值。

¥When two enums have the same declared names and enum member names, they were previously always considered compatible; however, when the values were known, TypeScript would silently allow them to have differing values.

TypeScript 5.4 通过要求已知值时必须相同来加强此限制。

¥TypeScript 5.4 tightens this restriction by requiring the values to be identical when they are known.

ts
namespace First {
export enum SomeEnum {
A = 0,
B = 1,
}
}
namespace Second {
export enum SomeEnum {
A = 0,
B = 2,
}
}
function foo(x: First.SomeEnum, y: Second.SomeEnum) {
// Both used to be compatible - no longer the case,
// TypeScript errors with something like:
//
// Each declaration of 'SomeEnum.B' differs in its value, where '1' was expected but '2' was given.
x = y;
y = x;
}

此外,当枚举成员之一没有静态已知值时,存在新的限制。在这些情况下,另一个枚举必须至少是隐式数字(例如,它没有静态解析的初始化器),或者它是显式数字(这意味着 TypeScript 可以将值解析为数字)。实际上,这意味着字符串枚举成员只能与具有相同值的其他字符串枚举兼容。

¥Additionally, there are new restrictions for when one of the enum members does not have a statically known value. In these cases, the other enum must at least be implicitly numeric (e.g. it has no statically resolved initializer), or it is explicitly numeric (meaning TypeScript could resolve the value to something numeric). Practically speaking, what this means is that string enum members are only ever compatible with other string enums of the same value.

ts
namespace First {
export declare enum SomeEnum {
A,
B,
}
}
namespace Second {
export declare enum SomeEnum {
A,
B = "some known string",
}
}
function foo(x: First.SomeEnum, y: Second.SomeEnum) {
// Both used to be compatible - no longer the case,
// TypeScript errors with something like:
//
// One value of 'SomeEnum.B' is the string '"some known string"', and the other is assumed to be an unknown numeric value.
x = y;
y = x;
}

更多信息请见 查看引入此变更的拉取请求

¥For more information, see the pull request that introduced this change.

枚举成员的名称限制

¥Name Restrictions on Enum Members

TypeScript 不再允许枚举成员使用名称 Infinity-InfinityNaN

¥TypeScript no longer allows enum members to use the names Infinity, -Infinity, or NaN.

ts
// Errors on all of these:
//
// An enum member cannot have a numeric name.
enum E {
Infinity = 0,
"-Infinity" = 1,
NaN = 2,
}

查看此处更多详细信息

¥See more details here.

更好地保存带有 any 剩余元素的元组的映射类型

¥Better Mapped Type Preservation Over Tuples with any Rest Elements

以前,将带有 any 的映射类型应用于元组会创建 any 元素类型。这是不理想的,现已修复。

¥Previously, applying a mapped type with any into a tuple would create an any element type. This is undesirable and is now fixed.

ts
Promise.all(["", ...([] as any)])
.then((result) => {
const head = result[0]; // 5.3: any, 5.4: string
const tail = result.slice(1); // 5.3 any, 5.4: any[]
});

更多信息,请参阅 修复 以及 关于行为变更的后续讨论进一步调整

¥For more information, see the fix along with the follow-on discussion around behavioral changes and further tweaks.

Emit 变更

¥Emit Changes

虽然本质上并非破坏性变更,但开发者可能隐式地依赖于 TypeScript 的 JavaScript 或声明触发的输出。以下是值得注意的变化。

¥While not a breaking change per se, developers may have implicitly taken dependencies on TypeScript’s JavaScript or declaration emit outputs. The following are notable changes.