TypeScript 4.9

satisfies 运算符

¥The satisfies Operator

TypeScript 开发者经常面临一个困境:我们希望确保某个表达式匹配某种类型,但同时也希望保留该表达式最具体的类型以便进行推断。

¥TypeScript developers are often faced with a dilemma: we want to ensure that some expression matches some type, but also want to keep the most specific type of that expression for inference purposes.

例如:

¥For example:

ts
// Each property can be a string or an RGB tuple.
const palette = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255]
// ^^^^ sacrebleu - we've made a typo!
};
// We want to be able to use string methods on 'green'...
const greenNormalized = palette.green.toUpperCase();

请注意,我们编写的是 bleu,而我们可能应该编写的是 blue。我们可以尝试通过在 palette 上使用类型注解来捕获 bleu 的拼写错误,但这样会丢失每个属性的信息。

¥Notice that we’ve written bleu, whereas we probably should have written blue. We could try to catch that bleu typo by using a type annotation on palette, but we’d lose the information about each property.

ts
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette: Record<Colors, string | RGB> = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255]
// ~~~~ The typo is now correctly detected
};
// But we now have an undesirable error here - 'palette.green' "could" be of type RGB and
// property 'toUpperCase' does not exist on type 'string | RGB'.
const greenNormalized = palette.green.toUpperCase();

新的 satisfies 运算符允许我们验证表达式的类型是否与某种类型匹配,而无需更改该表达式的结果类型。例如,我们可以使用 satisfies 来验证 palette 的所有属性是否与 string | number[] 兼容:

¥The new satisfies operator lets us validate that the type of an expression matches some type, without changing the resulting type of that expression. As an example, we could use satisfies to validate that all the properties of palette are compatible with string | number[]:

ts
type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255]
// ~~~~ The typo is now caught!
} satisfies Record<Colors, string | RGB>;
// toUpperCase() method is still accessible!
const greenNormalized = palette.green.toUpperCase();

satisfies 可用于捕获许多可能的错误。例如,我们可以确保一个对象包含某种类型的所有键,但仅此而已:

¥satisfies can be used to catch lots of possible errors. For example, we could ensure that an object has all the keys of some type, but no more:

ts
type Colors = "red" | "green" | "blue";
// Ensure that we have exactly the keys from 'Colors'.
const favoriteColors = {
"red": "yes",
"green": false,
"blue": "kinda",
"platypus": false
// ~~~~~~~~~~ error - "platypus" was never listed in 'Colors'.
} satisfies Record<Colors, unknown>;
// All the information about the 'red', 'green', and 'blue' properties are retained.
const g: boolean = favoriteColors.green;

也许我们不关心属性名称是否匹配,但我们关心每个属性的类型。在这种情况下,我们还可以确保对象的所有属性值都符合某种类型。

¥Maybe we don’t care about if the property names match up somehow, but we do care about the types of each property. In that case, we can also ensure that all of an object’s property values conform to some type.

ts
type RGB = [red: number, green: number, blue: number];
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0]
// ~~~~~~ error!
} satisfies Record<string, string | RGB>;
// Information about each property is still maintained.
const redComponent = palette.red.at(0);
const greenNormalized = palette.green.toUpperCase();

更多示例,请参阅 提出此提案的问题实现拉取请求。我们要感谢 Oleksandr Tarasiuk 与我们一起实现并迭代了此功能。

¥For more examples, you can see the issue proposing this and the implementing pull request. We’d like to thank Oleksandr Tarasiuk who implemented and iterated on this feature with us.

使用 in 运算符缩小未列出的属性范围

¥Unlisted Property Narrowing with the in Operator

作为开发者,我们经常需要处理运行时无法完全知道的值。事实上,我们经常不知道属性是否存在,不知道我们是从服务器获取响应还是读取配置文件。JavaScript 的 in 运算符可以检查对象上是否存在某个属性。

¥As developers, we often need to deal with values that aren’t fully known at runtime. In fact, we often don’t know if properties exist, whether we’re getting a response from a server or reading a configuration file. JavaScript’s in operator can check whether a property exists on an object.

以前,TypeScript 允许我们缩小未明确列出属性的类型范围。

¥Previously, TypeScript allowed us to narrow away any types that don’t explicitly list a property.

ts
interface RGB {
red: number;
green: number;
blue: number;
}
interface HSV {
hue: number;
saturation: number;
value: number;
}
function setColor(color: RGB | HSV) {
if ("hue" in color) {
// 'color' now has the type HSV
}
// ...
}

这里,类型 RGB 没有列出 hue,因此范围被缩小,只剩下类型 HSV

¥Here, the type RGB didn’t list the hue and got narrowed away, and leaving us with the type HSV.

但是,如果没有类型列出给定属性的示例该怎么办?在这些情况下,语言本身并没有给我们带来太大帮助。让我们在 JavaScript 中举个例子:

¥But what about examples where no type listed a given property? In those cases, the language didn’t help us much. Let’s take the following example in JavaScript:

js
function tryGetPackageName(context) {
const packageJSON = context.packageJSON;
// Check to see if we have an object.
if (packageJSON && typeof packageJSON === "object") {
// Check to see if it has a string name property.
if ("name" in packageJSON && typeof packageJSON.name === "string") {
return packageJSON.name;
}
}
return undefined;
}

将其重写为规范的 TypeScript 只需为 context; 定义和使用一个类型即可。但是,为 packageJSON 属性选择像 unknown 这样的安全类型会在旧版本的 TypeScript 中引发问题。

¥Rewriting this to canonical TypeScript would just be a matter of defining and using a type for context; however, picking a safe type like unknown for the packageJSON property would cause issues in older versions of TypeScript.

ts
interface Context {
packageJSON: unknown;
}
function tryGetPackageName(context: Context) {
const packageJSON = context.packageJSON;
// Check to see if we have an object.
if (packageJSON && typeof packageJSON === "object") {
// Check to see if it has a string name property.
if ("name" in packageJSON && typeof packageJSON.name === "string") {
// ~~~~
// error! Property 'name' does not exist on type 'object.
return packageJSON.name;
// ~~~~
// error! Property 'name' does not exist on type 'object.
}
}
return undefined;
}

这是因为虽然 packageJSON 的类型从 unknown 缩小到 object,但 in 运算符严格缩小到实际定义被检查属性的类型。因此,packageJSON 的类型仍然是 object

¥This is because while the type of packageJSON was narrowed from unknown to object, the in operator strictly narrowed to types that actually defined the property being checked. As a result, the type of packageJSON remained object.

TypeScript 4.9 使 in 运算符在缩小完全不列出属性的类型时更加强大。TypeScript 不会保留原样,而是会将其类型与 Record<"property-key-being-checked", unknown> 进行交叉。

¥TypeScript 4.9 makes the in operator a little bit more powerful when narrowing types that don’t list the property at all. Instead of leaving them as-is, the language will intersect their types with Record<"property-key-being-checked", unknown>.

因此,在我们的示例中,packageJSON 的类型将从 unknown 缩小到 object 再缩小到 object & Record<"name", unknown>。这样,我们就可以直接访问 packageJSON.name,并独立地缩小它。

¥So in our example, packageJSON will have its type narrowed from unknown to object to object & Record<"name", unknown> That allows us to access packageJSON.name directly and narrow that independently.

ts
interface Context {
packageJSON: unknown;
}
function tryGetPackageName(context: Context): string | undefined {
const packageJSON = context.packageJSON;
// Check to see if we have an object.
if (packageJSON && typeof packageJSON === "object") {
// Check to see if it has a string name property.
if ("name" in packageJSON && typeof packageJSON.name === "string") {
// Just works!
return packageJSON.name;
}
}
return undefined;
}

TypeScript 4.9 还加强了一些关于 in 使用方式的检查,确保左侧可以赋值给类型 string | number | symbol,右侧可以赋值给 object。这有助于检查我们使用的属性键是否有效,而不是意外地检查原语。

¥TypeScript 4.9 also tightens up a few checks around how in is used, ensuring that the left side is assignable to the type string | number | symbol, and the right side is assignable to object. This helps check that we’re using valid property keys, and not accidentally checking primitives.

详情请见 阅读实现拉取请求

¥For more information, read the implementing pull request

类中的自动访问器

¥Auto-Accessors in Classes

TypeScript 4.9 支持 ECMAScript 即将推出的一项名为自动访问器的功能。自动访问器的声明方式与类的属性类似,只是它们使用 accessor 关键字声明。

¥TypeScript 4.9 supports an upcoming feature in ECMAScript called auto-accessors. Auto-accessors are declared just like properties on classes, except that they’re declared with the accessor keyword.

ts
class Person {
accessor name: string;
constructor(name: string) {
this.name = name;
}
}

在幕后,这些自动访问器将 “de-sugar” 转换为具有不可访问的私有属性的 getset 访问器。

¥Under the covers, these auto-accessors “de-sugar” to a get and set accessor with an unreachable private property.

ts
class Person {
#__name: string;
get name() {
return this.#__name;
}
set name(value: string) {
this.#__name = value;
}
constructor(name: string) {
this.name = name;
}
}

你可以 在原始 PR 中阅读有关自动访问器拉取请求的更多信息

¥You can read up more about the auto-accessors pull request on the original PR.

NaN 上的相等性检查

¥Checks For Equality on NaN

JavaScript 开发者面临的一个主要问题是使用内置相等运算符检查值 NaN

¥A major gotcha for JavaScript developers is checking against the value NaN using the built-in equality operators.

背景信息:NaN 是一个特殊的数值,代表 “不是数字”。没有任何东西等于 NaN - 甚至 NaN

¥For some background, NaN is a special numeric value that stands for “Not a Number”. Nothing is ever equal to NaN - even NaN!

js
console.log(NaN == 0) // false
console.log(NaN === 0) // false
console.log(NaN == NaN) // false
console.log(NaN === NaN) // false

但至少对称地,所有内容始终不等于 NaN

¥But at least symmetrically everything is always not-equal to NaN.

js
console.log(NaN != 0) // true
console.log(NaN !== 0) // true
console.log(NaN != NaN) // true
console.log(NaN !== NaN) // true

从技术上讲,这并非 JavaScript 特有的问题,因为任何包含 IEEE-754 浮点数的语言都具有相同的行为;但 JavaScript 的主要数字类型是浮点数,JavaScript 中的数字解析通常会导致 NaN。检查 NaN 变得相当普遍,正确​​的方法是使用 Number.isNaN - 但正如我们所提到的,很多人最终会不小心使用 someValue === NaN 进行检查。

¥This technically isn’t a JavaScript-specific problem, since any language that contains IEEE-754 floats has the same behavior; but JavaScript’s primary numeric type is a floating point number, and number parsing in JavaScript can often result in NaN. In turn, checking against NaN ends up being fairly common, and the correct way to do so is to use Number.isNaN - but as we mentioned, lots of people accidentally end up checking with someValue === NaN instead.

TypeScript 现在会在与 NaN 直接比较时出错,并建议使用 Number.isNaN 的某种变体。

¥TypeScript now errors on direct comparisons against NaN, and will suggest using some variation of Number.isNaN instead.

ts
function validate(someValue: number) {
return someValue !== NaN;
// ~~~~~~~~~~~~~~~~~
// error: This condition will always return 'true'.
// Did you mean '!Number.isNaN(someValue)'?
}

我们认为这项更改应该严格地有助于捕获初学者的错误,类似于 TypeScript 目前在与对象和数组字面量进行比较时触发的错误。

¥We believe that this change should strictly help catch beginner errors, similar to how TypeScript currently issues errors on comparisons against object and array literals.

我们要感谢 Oleksandr Tarasiuk 贡献了 贡献了此检查

¥We’d like to extend our thanks to Oleksandr Tarasiuk who contributed this check.

文件监视现在使用文件系统事件

¥File-Watching Now Uses File System Events

在早期版本中,TypeScript 严重依赖轮询来监视单个文件。使用轮询策略意味着定期检查文件状态以获取更新。在 Node.js 中,fs.watchFile 是获取轮询文件监视程序的内置方法。虽然轮询在跨平台和文件系统时往往更具可预测性,但这意味着你的 CPU 必须定期中断并检查文件更新,即使没有任何更改也是如此。对于几十个文件,这可能不会引人注意;但是对于包含大量文件的大型项目来说 - 或者 node_modules 中有很多文件 - 这可能会占用大量资源。

¥In earlier versions, TypeScript leaned heavily on polling for watching individual files. Using a polling strategy meant checking the state of a file periodically for updates. On Node.js, fs.watchFile is the built-in way to get a polling file-watcher. While polling tends to be more predictable across platforms and file systems, it means that your CPU has to periodically get interrupted and check for updates to the file, even when nothing’s changed. For a few dozen files, this might not be noticeable; but on a bigger project with lots of files - or lots of files in node_modules - this can become a resource hog.

一般来说,更好的方法是使用文件系统事件。我们可以宣布我们关注特定文件的更新,并在这些文件实际发生更改时提供回调,而无需进行轮询。大多数正在使用的现代平台都提供类似 CreateIoCompletionPortkqueueepollinotify 的功能和 API。Node.js 通过提供 fs.watch 来抽象这些。文件系统事件通常效果很好,但使用它们需要注意一些细节,而使用 fs.watch API 则需要注意一些细节。监视器需要仔细考虑 inode 监控在某些文件系统上不可用(例如网络文件系统)、是否支持递归文件观察、目录重命名是否会触发事件,甚至文件监视器是否耗尽!换句话说,这并非免费的午餐,尤其是在你寻求跨平台功能时。

¥Generally speaking, a better approach is to use file system events. Instead of polling, we can announce that we’re interested in updates of specific files and provide a callback for when those files actually do change. Most modern platforms in use provide facilities and APIs like CreateIoCompletionPort, kqueue, epoll, and inotify. Node.js mostly abstracts these away by providing fs.watch. File system events usually work great, but there are lots of caveats to using them, and in turn, to using the fs.watch API. A watcher needs to be careful to consider inode watching, unavailability on certain file systems (e.g.networked file systems), whether recursive file watching is available, whether directory renames trigger events, and even file watcher exhaustion! In other words, it’s not quite a free lunch, especially if you’re looking for something cross-platform.

因此,我们的默认设置是选择最小公分母:轮询。并非总是如此,但大多数情况下如此。

¥As a result, our default was to pick the lowest common denominator: polling. Not always, but most of the time.

随着时间的推移,我们提供了实现 选择其他文件监视策略 的方法。这使我们能够获得反馈,并针对大多数特定于平台的陷阱强化我们的文件监视实现。由于 TypeScript 需要扩展到更大的代码库,并且在这方面有所改进,我们认为将文件系统事件切换为默认设置是一项值得的投资。

¥Over time, we’ve provided the means to choose other file-watching strategies. This allowed us to get feedback and harden our file-watching implementation against most of these platform-specific gotchas. As TypeScript has needed to scale to larger codebases, and has improved in this area, we felt swapping to file system events as the default would be a worthwhile investment.

在 TypeScript 4.9 中,文件监视默认由文件系统事件驱动,只有在我们无法设置基于事件的监视程序时才会回退到轮询。对于大多数开发者来说,在 --watch 模式下运行,或使用 TypeScript 支持的编辑器(如 Visual Studio 或 VS Code)运行时,这应该会提供更少的资源占用体验。

¥In TypeScript 4.9, file watching is powered by file system events by default, only falling back to polling if we fail to set up event-based watchers. For most developers, this should provide a much less resource-intensive experience when running in --watch mode, or running with a TypeScript-powered editor like Visual Studio or VS Code.

文件监视的工作方式仍然可以配置 通过环境变量和 watchOptions 传递参数 - 和 一些编辑器(例如 VS Code)可以独立支持 watchOptions。如果开发者使用更奇特的设置,将源代码驻留在网络文件系统(如 NFS 和 SMB)上,则可能需要恢复旧的行为;但是,如果服务器具有合理的处理能力,最好启用 SSH 并远程运行 TypeScript,以便它可以直接访问本地文件。VS Code 提供了大量的 远程扩展 库来简化此操作。

¥The way file-watching works can still be configured through environment variables and watchOptions - and some editors like VS Code can support watchOptions independently. Developers using more exotic set-ups where source code resides on a networked file systems (like NFS and SMB) may need to opt back into the older behavior; though if a server has reasonable processing power, it might just be better to enable SSH and run TypeScript remotely so that it has direct local file access. VS Code has plenty of remote extensions to make this easier.

你可以 在 GitHub 上阅读更多有关此变更的信息

¥You can read up more on this change on GitHub.

编辑器的 “删除未使用的导入” 和 “对导入进行排序” 命令

¥“Remove Unused Imports” and “Sort Imports” Commands for Editors

以前,TypeScript 仅支持两个编辑器命令来管理导入。例如,以下代码为例:

¥Previously, TypeScript only supported two editor commands to manage imports. For our examples, take the following code:

ts
import { Zebra, Moose, HoneyBadger } from "./zoo";
import { foo, bar } from "./helper";
let x: Moose | HoneyBadger = foo();

第一种被称为 “整理导入” ,它会删除未使用的导入,然后对剩余的导入进行排序。它会将该文件重写成如下所示:

¥The first was called “Organize Imports” which would remove unused imports, and then sort the remaining ones. It would rewrite that file to look like this one:

ts
import { foo } from "./helper";
import { HoneyBadger, Moose } from "./zoo";
let x: Moose | HoneyBadger = foo();

在 TypeScript 4.3 中,我们引入了一个名为 “对导入进行排序” 的命令,它只会对文件中的导入进行排序,而不会删除它们。 - 并将像这样重写文件。

¥In TypeScript 4.3, we introduced a command called “Sort Imports” which would only sort imports in the file, but not remove them - and would rewrite the file like this.

ts
import { bar, foo } from "./helper";
import { HoneyBadger, Moose, Zebra } from "./zoo";
let x: Moose | HoneyBadger = foo();

“对导入进行排序” 的需要注意的是,在 Visual Studio Code 中,此功能仅作为保存时命令提供。 - 不是可手动触发的命令。

¥The caveat with “Sort Imports” was that in Visual Studio Code, this feature was only available as an on-save command - not as a manually triggerable command.

TypeScript 4.9 添加了另一半,现在提供 “删除未使用的导入”。TypeScript 现在将删除未使用的导入名称和语句,但其他方面将保留相对顺序。

¥TypeScript 4.9 adds the other half, and now provides “Remove Unused Imports”. TypeScript will now remove unused import names and statements, but will otherwise leave the relative ordering alone.

ts
import { Moose, HoneyBadger } from "./zoo";
import { foo } from "./helper";
let x: Moose | HoneyBadger = foo();

此功能适用于所有希望使用任一命令的编辑器;但值得注意的是,Visual Studio Code(1.73 及更高版本)将内置支持这些命令,并将通过其命令面板显示这些命令。喜欢使用更精细的 “删除未使用的导入” 或 “对导入进行排序” 命令的用户应该能够根据需要重新分配 “整理导入” 组合键。

¥This feature is available to all editors that wish to use either command; but notably, Visual Studio Code (1.73 and later) will have support built in and will surface these commands via its Command Palette. Users who prefer to use the more granular “Remove Unused Imports” or “Sort Imports” commands should be able to reassign the “Organize Imports” key combination to them if desired.

你可以 在此查看功能详情

¥You can view specifics of the feature here.

return 关键字的跳转到定义

¥Go-to-Definition on return Keywords

在编辑器中,当对 return 关键字运行 go-to-definition 时,TypeScript 现在会跳转到相应函数的顶部。这有助于快速了解 return 属于哪个函数。

¥In the editor, when running a go-to-definition on the return keyword, TypeScript will now jump you to the top of the corresponding function. This can be helpful to get a quick sense of which function a return belongs to.

我们预计 TypeScript 会将此功能扩展为更多关键字 例如 awaityieldswitchcasedefault

¥We expect TypeScript will expand this functionality to more keywords such as await and yield or switch, case, and default.

此功能已实现 感谢 Oleksandr Tarasiuk

¥This feature was implemented thanks to Oleksandr Tarasiuk.

性能改进

¥Performance Improvements

TypeScript 有一些虽小但显著的性能改进。

¥TypeScript has a few small, but notable, performance improvements.

首先,TypeScript 的 forEachChild 函数已被重写,在所有语法节点中使用函数表查找,而不是 switch 语句。forEachChild 是遍历编译器中语法节点的主要工具,在我们编译器的绑定阶段以及部分语言服务中被大量使用。forEachChild 的重构使我们在绑定阶段以及跨语言服务操作中花费的时间减少了高达 20%。

¥First, TypeScript’s forEachChild function has been rewritten to use a function table lookup instead of a switch statement across all syntax nodes. forEachChild is a workhorse for traversing syntax nodes in the compiler, and is used heavily in the binding stage of our compiler, along with parts of the language service. The refactoring of forEachChild yielded up to a 20% reduction of time spent in our binding phase and across language service operations.

在发现 forEachChild 的性能优势后,我们便在 visitEachChild 上进行了尝试,visitEachChild 是我们用于在编译器和语言服务中转换节点的函数。同样的重构使生成项目输出所花费的时间减少了高达 3%。

¥Once we discovered this performance win for forEachChild, we tried it out on visitEachChild, a function we use for transforming nodes in the compiler and language service. The same refactoring yielded up to a 3% reduction in time spent in generating project output.

forEachChild 中的初始探索是 Artemis Everfree 中的 受一篇博文启发。虽然我们有理由相信,速度提升的根本原因可能更多地与函数大小/复杂性有关,而非博客文章中描述的问题,但我们很庆幸能够从经验中吸取教训,并尝试了一种相对快速的重构方法,从而加快了 TypeScript 的速度。

¥The initial exploration in forEachChild was inspired by a blog post by Artemis Everfree. While we have some reason to believe the root cause of our speed-up might have more to do with function size/complexity than the issues described in the blog post, we’re grateful that we were able to learn from the experience and try out a relatively quick refactoring that made TypeScript faster.

最后,TypeScript 在条件类型的 true 分支中保存类型信息的方式已得到优化。在类似这样的类型中

¥Finally, the way TypeScript preserves the information about a type in the true branch of a conditional type has been optimized. In a type like

ts
interface Zoo<T extends Animal> {
// ...
}
type MakeZoo<A> = A extends Animal ? Zoo<A> : never;

在检查 Zoo<A> 是否有效时,TypeScript 必须确保 “remember” 也必须是 A。这基本上是通过创建一个特殊类型来实现的,该类型用于保存 AAnimal 的交叉;然而,TypeScript 以前会主动执行此操作,但这并非总是必要的。此外,我们的类型检查器中的一些错误代码阻止了这些特殊类型的简化。TypeScript 现在会推迟这些类型的交叉操作,直到必要时才进行。对于大量使用条件类型的代码库,你可能会看到 TypeScript 的显著加速,但在我们的性能测试套件中,我们发现类型检查时间仅减少了 3%。

¥TypeScript has to “remember” that A must also be an Animal when checking if Zoo<A> is valid. This is basically done by creating a special type that used to hold the intersection of A with Animal; however, TypeScript previously did this eagerly which isn’t always necessary. Furthermore, some faulty code in our type-checker prevented these special types from being simplified. TypeScript now defers intersecting these types until it’s necessary. For codebases with heavy use of conditional types, you might witness significant speed-ups with TypeScript, but in our performance testing suite, we saw a more modest 3% reduction in type-checking time.

你可以在各自的拉取请求中了解更多关于这些优化的信息:

¥You can read up more on these optimizations on their respective pull requests:

错误修复和重大变更

¥Correctness Fixes and Breaking Changes

lib.d.ts 更新

¥lib.d.ts Updates

虽然 TypeScript 努力避免重大破坏,但即使是内置库中的微小更改也可能导致问题。我们预计 DOM 和 lib.d.ts 更新不会带来重大影响,但可能会有一些小问题。

¥While TypeScript strives to avoid major breaks, even small changes in the built-in libraries can cause issues. We don’t expect major breaks as a result of DOM and lib.d.ts updates, but there may be some small ones.

更好的 Promise.resolve 类型

¥Better Types for Promise.resolve

Promise.resolve 现在使用 Awaited 类型来解包传递给它的类似 Promise 的类型。这意味着它通常返回正确的 Promise 类型,但如果它期望的是 anyunknown 而不是 Promise,那么改进的类型可能会破坏现有代码。更多信息请见 查看原始变更

¥Promise.resolve now uses the Awaited type to unwrap Promise-like types passed to it. This means that it more often returns the right Promise type, but that improved type can break existing code if it was expecting any or unknown instead of a Promise. For more information, see the original change.

JavaScript Emit 不再省略导入

¥JavaScript Emit No Longer Elides Imports

当 TypeScript 首次支持 JavaScript 的类型检查和编译时,它意外地支持了一项名为导入省略的功能。简而言之,如果导入未用作值,或者编译器在运行时检测到导入未引用值,则编译器将在触发期间删除该导入。

¥When TypeScript first supported type-checking and compilation for JavaScript, it accidentally supported a feature called import elision. In short, if an import is not used as a value, or the compiler can detect that the import doesn’t refer to a value at runtime, the compiler will drop the import during emit.

此行为值得怀疑,尤其是检测导入是否未引用值,因为这意味着 TypeScript 必须信任有时不准确的声明文件。TypeScript 现在会在 JavaScript 文件中保留导入。

¥This behavior was questionable, especially the detection of whether the import doesn’t refer to a value, since it means that TypeScript has to trust sometimes-inaccurate declaration files. In turn, TypeScript now preserves imports in JavaScript files.

js
// Input:
import { someValue, SomeClass } from "some-module";
/** @type {SomeClass} */
let val = someValue;
// Previous Output:
import { someValue } from "some-module";
/** @type {SomeClass} */
let val = someValue;
// Current Output:
import { someValue, SomeClass } from "some-module";
/** @type {SomeClass} */
let val = someValue;

更多信息请参见 实现变更

¥More information is available at the implementing change.

exports 优先级高于 typesVersions

¥exports is Prioritized Over typesVersions

以前,TypeScript 在解析 --moduleResolution node16 下的 package.json 时,错误地将 typesVersions 字段优先于 exports 字段。如果此更改影响你的库,你可能需要在 package.jsonexports 字段中添加 types@ 版本选择器。

¥Previously, TypeScript incorrectly prioritized the typesVersions field over the exports field when resolving through a package.json under --moduleResolution node16. If this change impacts your library, you may need to add types@ version selectors in your package.json’s exports field.

diff
{
"type": "module",
"main": "./dist/main.js"
"typesVersions": {
"<4.8": { ".": ["4.8-types/main.d.ts"] },
"*": { ".": ["modern-types/main.d.ts"] }
},
"exports": {
".": {
+ "types@<4.8": "./4.8-types/main.d.ts",
+ "types": "./modern-types/main.d.ts",
"import": "./dist/main.js"
}
}
}

更多信息请见 查看此拉取请求

¥For more information, see this pull request.

substituteSubstitutionType 中替换为 constraint

¥substitute Replaced With constraint on SubstitutionTypes

作为替换类型优化的一部分,SubstitutionType 对象不再包含表示有效替换(通常是基类型和隐式约束的交叉)的 substitute 属性。 - 而是只需包含 constraint 属性即可。

¥As part of an optimization on substitution types, SubstitutionType objects no longer contain the substitute property representing the effective substitution (usually an intersection of the base type and the implicit constraint) - instead, they just contain the constraint property.

详情请见 在原始拉取请求中阅读更多内容

¥For more details, read more on the original pull request.