TypeScript 3.9

推断和 Promise.all 的改进

🌐 Improvements in Inference and Promise.all

TypeScript 的最新版本(大约 3.7)对像 Promise.allPromise.race 这样的函数声明进行了更新。不幸的是,这引入了一些回归问题,尤其是在将值与 nullundefined 混合使用时。

🌐 Recent versions of TypeScript (around 3.7) have had updates to the declarations of functions like Promise.all and Promise.race. Unfortunately, that introduced a few regressions, especially when mixing in values with null or undefined.

ts
interface Lion {
roar(): void;
}
interface Seal {
singKissFromARose(): void;
}
async function visitZoo(
lionExhibit: Promise<Lion>,
sealExhibit: Promise<Seal | undefined>
) {
let [lion, seal] = await Promise.all([lionExhibit, sealExhibit]);
lion.roar(); // uh oh
// ~~~~
// Object is possibly 'undefined'.
}

这是奇怪的行为! sealExhibit 包含 undefined 的事实不知怎么导致 lion 类型被污染,以至于包括了 undefined

🌐 This is strange behavior! The fact that sealExhibit contained an undefined somehow poisoned type of lion to include undefined.

感谢 Jack Bates 提交的 拉取请求,在 TypeScript 3.9 中我们的推断过程得到了改进并解决了这个问题。 上述问题现在不再报错。 如果你因为 Promise 相关的问题而一直被困在较旧版本的 TypeScript,我们鼓励你尝试一下 3.9!

🌐 Thanks to a pull request from Jack Bates, this has been fixed with improvements in our inference process in TypeScript 3.9. The above no longer errors. If you’ve been stuck on older versions of TypeScript due to issues around Promises, we encourage you to give 3.9 a shot!

awaited类型怎么样?

🌐 What About the awaited Type?

如果你一直关注我们的问题追踪器和设计会议记录,你可能已经注意到一些关于一种名为 awaited 的新类型运算符的工作。这个类型运算符的目标是准确地模拟 Promise 在 JavaScript 中的解包方式。

🌐 If you’ve been following our issue tracker and design meeting notes, you might be aware of some work around a new type operator called awaited. This goal of this type operator is to accurately model the way that Promise unwrapping works in JavaScript.

我们最初预计会在 TypeScript 3.9 中发布 awaited,但在我们用现有代码库运行早期的 TypeScript 构建时,我们意识到这个功能在正式推向所有人之前还需要更多的设计工作。 因此,我们决定先从主分支中移除该功能,直到我们更加有信心为止。 我们会继续对该功能进行实验,但不会在本次版本中发布它。

🌐 We initially anticipated shipping awaited in TypeScript 3.9, but as we’ve run early TypeScript builds with existing codebases, we’ve realized that the feature needs more design work before we can roll it out to everyone smoothly. As a result, we’ve decided to pull the feature out of our main branch until we feel more confident. We’ll be experimenting more with the feature, but we won’t be shipping it as part of this release.

速度改进

🌐 Speed Improvements

TypeScript 3.9 带来了许多新的速度改进。我们的团队在观察到使用像 material-ui 和 styled-components 这样的包时编辑/编译速度非常慢之后,一直专注于性能优化。我们深入研究了这里的问题,提出了一系列不同的拉取请求,以优化涉及大型联合类型、交叉类型、条件类型和映射类型的某些特殊情况。

🌐 TypeScript 3.9 ships with many new speed improvements. Our team has been focusing on performance after observing extremely poor editing/compilation speed with packages like material-ui and styled-components. We’ve dived deep here, with a series of different pull requests that optimize certain pathological cases involving large unions, intersections, conditional types, and mapped types.

每个这些拉取请求在某些代码库上大约能减少 5-10% 的编译时间。总的来说,我们相信已经实现了大约 40% 的 material-ui 编译时间减少!

🌐 Each of these pull requests gains about a 5-10% reduction in compile times on certain codebases. In total, we believe we’ve achieved around a 40% reduction in material-ui’s compile time!

我们在编辑器场景中的文件重命名功能也做了一些更改。我们从 Visual Studio Code 团队听说,在重命名文件时,仅确定哪些导入语句需要更新就可能花费 5 到 10 秒。TypeScript 3.9 通过更改编译器和语言服务缓存文件查找的内部机制来解决这个问题。

🌐 We also have some changes to file renaming functionality in editor scenarios. We heard from the Visual Studio Code team that when renaming a file, just figuring out which import statements needed to be updated could take between 5 to 10 seconds. TypeScript 3.9 addresses this issue by changing the internals of how the compiler and language service caches file lookups.

虽然仍有改进空间,但我们希望这项工作能为每个人带来更流畅的体验!

🌐 While there’s still room for improvement, we hope this work translates to a snappier experience for everyone!

// @ts-expect-error 评论

🌐 // @ts-expect-error Comments

想象一下,我们正在用 TypeScript 编写一个库,并且正在将一些名为 doStuff 的函数导出作为我们的公共 API 的一部分。这个函数的类型声明它接受两个 string,这样其他 TypeScript 用户就可以获得类型检查错误,但它也会进行运行时检查(可能只在开发构建中)以给 JavaScript 用户一个有用的错误提示。

🌐 Imagine that we’re writing a library in TypeScript and we’re exporting some function called doStuff as part of our public API. The function’s types declare that it takes two strings so that other TypeScript users can get type-checking errors, but it also does a runtime check (maybe only in development builds) to give JavaScript users a helpful error.

ts
function doStuff(abc: string, xyz: string) {
assert(typeof abc === "string");
assert(typeof xyz === "string");
// do some stuff
}

因此,当 TypeScript 用户误用此函数时,他们会看到一个有用的红色波浪线和错误信息,而 JavaScript 用户则会收到断言错误。 我们想要测试这种行为,所以我们将编写一个单元测试。

🌐 So TypeScript users will get a helpful red squiggle and an error message when they misuse this function, and JavaScript users will get an assertion error. We’d like to test this behavior, so we’ll write a unit test.

ts
expect(() => {
doStuff(123, 456);
}).toThrow();

不幸的是,如果我们的测试是用 TypeScript 编写的,TypeScript 会报错!

🌐 Unfortunately if our tests are written in TypeScript, TypeScript will give us an error!

ts
doStuff(123, 456);
// ~~~
// error: Type 'number' is not assignable to type 'string'.

这就是为什么 TypeScript 3.9 引入了一个新功能:// @ts-expect-error 注释。 当一行代码前有 // @ts-expect-error 注释时,TypeScript 会抑制该错误的报告; 但如果没有错误,TypeScript 会提示 // @ts-expect-error 是不必要的。

🌐 That’s why TypeScript 3.9 brings a new feature: // @ts-expect-error comments. When a line is preceded by a // @ts-expect-error comment, TypeScript will suppress that error from being reported; but if there’s no error, TypeScript will report that // @ts-expect-error wasn’t necessary.

举个简单的例子,以下代码是可以的。

🌐 As a quick example, the following code is okay

ts
// @ts-expect-error
console.log(47 * "octopus");

而以下代码

🌐 while the following code

ts
// @ts-expect-error
console.log(1 + 1);

导致错误

🌐 results in the error

Unused '@ts-expect-error' directive.

我们想向 Josh Goldberg 表示衷心的感谢,他是实现此功能的贡献者。有关更多信息,你可以查看 the ts-expect-error pull request

🌐 We’d like to extend a big thanks to Josh Goldberg, the contributor who implemented this feature. For more information, you can take a look at the ts-expect-error pull request.

ts-ignore还是ts-expect-error

🌐 ts-ignore or ts-expect-error?

在某些情况下,// @ts-expect-error 可以作为抑制注释,类似于 // @ts-ignore。不同之处在于,如果下一行没有错误,// @ts-ignore 什么也不会做。

🌐 In some ways // @ts-expect-error can act as a suppression comment, similar to // @ts-ignore. The difference is that // @ts-ignore will do nothing if the following line is error-free.

你可能会想把现有的 // @ts-ignore 注释改成 // @ts-expect-error,同时你可能也在想今后的代码应该用哪种。虽然这完全取决于你和你的团队,但我们对在某些情况下的选择有一些建议。

🌐 You might be tempted to switch existing // @ts-ignore comments over to // @ts-expect-error, and you might be wondering which is appropriate for future code. While it’s entirely up to you and your team, we have some ideas of which to pick in certain situations.

如果符合以下情况,请选择 ts-expect-error

🌐 Pick ts-expect-error if:

  • 你正在编写测试代码,而你实际上希望类型系统在操作时出错。
  • 你希望修复程序能够很快完成,并且你只需要一个快速的解决方法。
  • 你正在一个规模适中的项目中,并且有一个积极主动的团队,他们希望在受影响的代码恢复有效后立即删除抑制注释。

如果符合以下情况,请选择 ts-ignore

🌐 Pick ts-ignore if:

  • 你的项目规模较大,代码中出现了新的错误,但错误原因不明确。
  • 你正在升级两个不同版本的 TypeScript,并且一行代码在一个版本中出现错误,而在另一个版本中没有错误。
  • 你真的没有时间决定哪个选项更好。

条件表达式中的未调用函数检查

🌐 Uncalled Function Checks in Conditional Expressions

在 TypeScript 3.7 中,我们引入了 未调用函数检查,用于在你忘记调用函数时报告错误。

🌐 In TypeScript 3.7 we introduced uncalled function checks to report an error when you’ve forgotten to call a function.

ts
function hasImportantPermissions(): boolean {
// ...
}
// Oops!
if (hasImportantPermissions) {
// ~~~~~~~~~~~~~~~~~~~~~~~
// This condition will always return true since the function is always defined.
// Did you mean to call it instead?
deleteAllTheImportantFiles();
}

然而,这个错误只适用于 if 语句中的条件。感谢 Alexander Tarasyuk一个拉取请求,现在该功能也支持三元条件表达式(即 cond ? trueExpr : falseExpr 语法)。

🌐 However, this error only applied to conditions in if statements. Thanks to a pull request from Alexander Tarasyuk, this feature is also now supported in ternary conditionals (i.e. the cond ? trueExpr : falseExpr syntax).

ts
declare function listFilesOfDirectory(dirPath: string): string[];
declare function isDirectory(): boolean;
function getAllFiles(startFileName: string) {
const result: string[] = [];
traverse(startFileName);
return result;
function traverse(currentPath: string) {
return isDirectory
? // ~~~~~~~~~~~
// This condition will always return true
// since the function is always defined.
// Did you mean to call it instead?
listFilesOfDirectory(currentPath).forEach(traverse)
: result.push(currentPath);
}
}

https://github.com/microsoft/TypeScript/issues/36048

编辑器改进

🌐 Editor Improvements

TypeScript 编译器不仅支持大多数主要编辑器中的 TypeScript 编辑体验,还支持 Visual Studio 系列编辑器中的 JavaScript 体验以及更多功能。在编辑器中使用新的 TypeScript/JavaScript 功能会因编辑器而异,但

🌐 The TypeScript compiler not only powers the TypeScript editing experience in most major editors, it also powers the JavaScript experience in the Visual Studio family of editors and more. Using new TypeScript/JavaScript functionality in your editor will differ depending on your editor, but

JavaScript 中的 CommonJS 自动导入

🌐 CommonJS Auto-Imports in JavaScript

一项重大改进是使用 CommonJS 模块在 JavaScript 文件中自动导入。

🌐 One great new improvement is in auto-imports in JavaScript files using CommonJS modules.

在旧版本中,TypeScript 始终假设无论你的文件是什么,你都需要 ECMAScript 风格的导入,例如

🌐 In older versions, TypeScript always assumed that regardless of your file, you wanted an ECMAScript-style import like

js
import * as fs from "fs";

然而,并不是每个人在编写 JavaScript 文件时都以 ECMAScript 风格的模块为目标。很多用户仍然像这样使用 CommonJS 风格的 require(...) 导入

🌐 However, not everyone is targeting ECMAScript-style modules when writing JavaScript files. Plenty of users still use CommonJS-style require(...) imports like so

js
const fs = require("fs");

TypeScript 现在会自动检测你正在使用的导入类型,以保持文件样式简洁一致。

🌐 TypeScript now automatically detects the types of imports you’re using to keep your file’s style clean and consistent.

有关更改的更多详细信息,请参见相应的拉取请求

🌐 For more details on the change, see the corresponding pull request.

代码操作保留换行符

🌐 Code Actions Preserve Newlines

TypeScript 的重构和快速修复经常不能很好地保留换行符。作为一个非常基本的例子,看看以下代码。

🌐 TypeScript’s refactorings and quick fixes often didn’t do a great job of preserving newlines. As a really basic example, take the following code.

ts
const maxValue = 100;
/*start*/
for (let i = 0; i <= maxValue; i++) {
// First get the squared value.
let square = i ** 2;
// Now print the squared value.
console.log(square);
}
/*end*/

如果我们在编辑器中高亮从 /*start*//*end*/ 的范围以提取到一个新函数,我们最终会得到如下代码。

🌐 If we highlighted the range from /*start*/ to /*end*/ in our editor to extract to a new function, we’d end up with code like the following.

ts
const maxValue = 100;
printSquares();
function printSquares() {
for (let i = 0; i <= maxValue; i++) {
// First get the squared value.
let square = i ** 2;
// Now print the squared value.
console.log(square);
}
}

Extracting the for loop to a function in older versions of TypeScript. A newline is not preserved.

这不太理想——在我们的 for 循环中每条语句之间都有一个空行,但重构把它去掉了!TypeScript 3.9 做了更多的工作来保留我们写的内容。

🌐 That’s not ideal - we had a blank line between each statement in our for loop, but the refactoring got rid of it! TypeScript 3.9 does a little more work to preserve what we write.

ts
const maxValue = 100;
printSquares();
function printSquares() {
for (let i = 0; i <= maxValue; i++) {
// First get the squared value.
let square = i ** 2;
// Now print the squared value.
console.log(square);
}
}

Extracting the for loop to a function in TypeScript 3.9. A newline is preserved.

你可以在这个拉取请求中查看更多关于实现的内容

🌐 You can see more about the implementation in this pull request

快速修复缺失返回表达式的问题

🌐 Quick Fixes for Missing Return Expressions

有时我们可能会忘记返回函数中最后一条语句的值,尤其是在向箭头函数添加大括号时。

🌐 There are occasions where we might forget to return the value of the last statement in a function, especially when adding curly braces to arrow functions.

ts
// before
let f1 = () => 42;
// oops - not the same!
let f2 = () => {
42;
};

感谢社区成员 Wenlu Wang 提交的 拉取请求,TypeScript 现在可以提供快速修复,以添加缺失的 return 语句、移除大括号,或者为看起来像对象字面量的箭头函数体添加括号。

🌐 Thanks to a pull request from community member Wenlu Wang, TypeScript can provide a quick-fix to add missing return statements, remove curly braces, or add parentheses to arrow function bodies that look suspiciously like object literals.

TypeScript fixing an error where no expression is returned by adding a return statement or removing curly braces.

支持“解决方案样式”tsconfig.json 文件

🌐 Support for “Solution Style” tsconfig.json Files

编辑需要弄清楚一个文件属于哪个配置文件,以便可以应用适当的选项,并找出当前“项目”中包含了哪些其他文件。默认情况下,由 TypeScript 语言服务器驱动的编辑器通过向上遍历每个父目录来查找 tsconfig.json 来实现这一点。

🌐 Editors need to figure out which configuration file a file belongs to so that it can apply the appropriate options and figure out which other files are included in the current “project”. By default, editors powered by TypeScript’s language server do this by walking up each parent directory to find a tsconfig.json.

一个稍微不太适用的情况是,当一个 tsconfig.json 仅仅存在于引用其他 tsconfig.json 文件时。

🌐 One case where this slightly fell over is when a tsconfig.json simply existed to reference other tsconfig.json files.

// tsconfig.json
{
"": [],
{ "path": "./tsconfig.shared.json" },
{ "path": "./tsconfig.frontend.json" },
{ "path": "./tsconfig.backend.json" }
]
}

这个实际上只用来管理其他项目文件的文件在某些环境中通常被称为“解决方案”。 在这里,这些 tsconfig.*.json 文件不会被服务器识别,但我们真的希望语言服务器能够理解当前的 .ts 文件很可能属于这个根 tsconfig.json 中提到的某个项目。

🌐 This file that really does nothing but manage other project files is often called a “solution” in some environments. Here, none of these tsconfig.*.json files get picked up by the server, but we’d really like the language server to understand that the current .ts file probably belongs to one of the mentioned projects in this root tsconfig.json.

TypeScript 3.9 为此配置的编辑场景增加了支持。有关更多详细信息,请查看添加此功能的拉取请求

🌐 TypeScript 3.9 adds support to editing scenarios for this configuration. For more details, take a look at the pull request that added this functionality.

打破变更

🌐 Breaking Changes

解析可选链式和非空断言的差异

🌐 Parsing Differences in Optional Chaining and Non-Null Assertions

TypeScript 最近实现了可选链操作符,但我们收到用户反馈,指出可选链(?.)与非空断言操作符(`!“)的行为非常反直觉。

🌐 TypeScript recently implemented the optional chaining operator, but we’ve received user feedback that the behavior of optional chaining (?.) with the non-null assertion operator (!) is extremely counter-intuitive.

具体来说,在之前的版本中,代码如下:

🌐 Specifically, in previous versions, the code

ts
foo?.bar!.baz;

被解释为等同于以下 JavaScript。

🌐 was interpreted to be equivalent to the following JavaScript.

js
(foo?.bar).baz;

在上面的代码中,括号阻止了可选链的“短路”行为,因此如果 fooundefined,访问 baz 将导致运行时错误。

🌐 In the above code the parentheses stop the “short-circuiting” behavior of optional chaining, so if foo is undefined, accessing baz will cause a runtime error.

指出这种行为的 Babel 团队,以及大多数向我们提供反馈的用户,都认为这种行为是错误的。我们也是!我们听到最多的是,! 操作符应该直接“消失”,因为其意图是从 bar 的类型中移除 nullundefined

🌐 The Babel team who pointed this behavior out, and most users who provided feedback to us, believe that this behavior is wrong. We do too! The thing we heard the most was that the ! operator should just “disappear” since the intent was to remove null and undefined from the type of bar.

换句话说,大多数人认为原始代码片段应该解释为

🌐 In other words, most people felt that the original snippet should be interpreted as

js
foo?.bar.baz;

fooundefined 时,它的值就是 undefined

🌐 which just evaluates to undefined when foo is undefined.

这是一个重大变更,但我们相信大多数代码在编写时都是按照新的理解方式来的。希望恢复旧行为的用户可以在 ! 运算符的左侧加上明确的括号。

🌐 This is a breaking change, but we believe most code was written with the new interpretation in mind. Users who want to revert to the old behavior can add explicit parentheses around the left side of the ! operator.

ts
foo?.bar!.baz;

}> 现在是无效的 JSX 文本字符

🌐 } and > are Now Invalid JSX Text Characters

JSX 规范禁止在文本位置使用 }> 字符。TypeScript 和 Babel 都决定强制执行这一规则以更好地符合规范。插入这些字符的新方法是使用 HTML 转义码(例如 <span> 2 &gt 1 </span>)或插入带有字符串字面量的表达式(例如 <span> 2 {">"} 1 </span>)。

🌐 The JSX Specification forbids the use of the } and > characters in text positions. TypeScript and Babel have both decided to enforce this rule to be more conformant. The new way to insert these characters is to use an HTML escape code (e.g. <span> 2 &gt 1 </span>) or insert an expression with a string literal (e.g. <span> 2 {">"} 1 </span>).

幸运的是,多亏了来自 Brad Zacher拉取请求 强制执行这一点,你将收到类似于以下内容的错误消息

🌐 Luckily, thanks to the pull request enforcing this from Brad Zacher, you’ll get an error message along the lines of

Unexpected token. Did you mean `{'>'}` or `>`?
Unexpected token. Did you mean `{'}'}` or `}`?

例如:

🌐 For example:

tsx
let directions = <span>Navigate to: Menu Bar > Tools > Options</span>;
// ~ ~
// Unexpected token. Did you mean `{'>'}` or `>`?

该错误信息附带了一个方便的快速修复,并且多亏了 Alexander Tarasyuk如果你有很多错误,可以批量应用这些更改

🌐 That error message came with a handy quick fix, and thanks to Alexander Tarasyuk, you can apply these changes in bulk if you have a lot of errors.

对交叉和可选属性的更严格检查

🌐 Stricter Checks on Intersections and Optional Properties

通常,如果 AB 可以赋值给 C,类似 A & B 的交叉类型就可以赋值给 C;然而,有时这在处理可选属性时会出现问题。例如,看看下面的例子:

🌐 Generally, an intersection type like A & B is assignable to C if either A or B is assignable to C; however, sometimes that has problems with optional properties. For example, take the following:

ts
interface A {
a: number; // notice this is 'number'
}
interface B {
b: string;
}
interface C {
a?: boolean; // notice this is 'boolean'
b: string;
}
declare let x: A & B;
declare let y: C;
y = x;

在以前的 TypeScript 版本中,这是允许的,因为虽然 AC 完全不兼容,但 BC 是兼容的。

🌐 In previous versions of TypeScript, this was allowed because while A was totally incompatible with C, B was compatible with C.

在 TypeScript 3.9 中,只要交叉类型中的每个类型都是具体的对象类型,类型系统就会一次性考虑所有属性。因此,TypeScript 会发现 A & Ba 属性与 C 的属性不兼容:

🌐 In TypeScript 3.9, so long as every type in an intersection is a concrete object type, the type system will consider all of the properties at once. As a result, TypeScript will see that the a property of A & B is incompatible with that of C:

Type 'A & B' is not assignable to type 'C'.
Types of property 'a' are incompatible.
Type 'number' is not assignable to type 'boolean | undefined'.

有关此更改的更多信息,请参阅相应的拉取请求

🌐 For more information on this change, see the corresponding pull request.

通过判别属性减少交叉

🌐 Intersections Reduced By Discriminant Properties

有一些情况,你可能会得到描述实际上不存在的值的类型。例如

🌐 There are a few cases where you might end up with types that describe values that just don’t exist. For example

ts
declare function smushObjects<T, U>(x: T, y: U): T & U;
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
declare let x: Circle;
declare let y: Square;
let z = smushObjects(x, y);
console.log(z.kind);

这段代码有点奇怪,因为实际上没有办法创建 CircleSquare 的交集——它们有两个不兼容的 kind 字段。在以前的 TypeScript 版本中,这段代码是允许的,并且 kind 本身的类型是 never,因为 "circle" & "square" 描述了一组可以 never 存在的值。

🌐 This code is slightly weird because there’s really no way to create an intersection of a Circle and a Square - they have two incompatible kind fields. In previous versions of TypeScript, this code was allowed and the type of kind itself was never because "circle" & "square" described a set of values that could never exist.

在 TypeScript 3.9 中,类型系统在这里变得更激进——它注意到由于它们的 kind 属性,不可能交叉 CircleSquare。 因此,它不是将 z.kind 的类型折叠为 never,而是将 z 本身(Circle & Square)的类型折叠为 never。 这意味着上述代码现在会报错:

🌐 In TypeScript 3.9, the type system is more aggressive here - it notices that it’s impossible to intersect Circle and Square because of their kind properties. So instead of collapsing the type of z.kind to never, it collapses the type of z itself (Circle & Square) to never. That means the above code now errors with:

Property 'kind' does not exist on type 'never'.

我们观察到的大多数中断似乎都与类型声明略有不正确有关。 更多详细信息,请参见原始拉取请求

🌐 Most of the breaks we observed seem to correspond with slightly incorrect type declarations. For more details, see the original pull request.

Getter/Setter 不再可枚举

🌐 Getters/Setters are No Longer Enumerable

在旧版本的 TypeScript 中,类中的 getset 访问器的生成方式使它们可以被枚举;然而,这并不符合 ECMAScript 规范,该规范规定它们必须是不可枚举的。因此,针对 ES5 和 ES2015 的 TypeScript 代码可能在行为上有所不同。

🌐 In older versions of TypeScript, get and set accessors in classes were emitted in a way that made them enumerable; however, this wasn’t compliant with the ECMAScript specification which states that they must be non-enumerable. As a result, TypeScript code that targeted ES5 and ES2015 could differ in behavior.

感谢 GitHub 用户 pathurs拉取请求,TypeScript 3.9 在这方面现在与 ECMAScript 更加一致。

🌐 Thanks to a pull request from GitHub user pathurs, TypeScript 3.9 now conforms more closely with ECMAScript in this regard.

扩展 any 的类型参数不再作为 any 起作用

🌐 Type Parameters That Extend any No Longer Act as any

在以前的 TypeScript 版本中,约束为 any 的类型参数可以被视为 any

🌐 In previous versions of TypeScript, a type parameter constrained to any could be treated as any.

ts
function foo<T extends any>(arg: T) {
arg.spfjgerijghoied; // no error!
}

这是一个疏忽,因此 TypeScript 3.9 采取了更为保守的方法,并对这些可疑操作触发错误。

🌐 This was an oversight, so TypeScript 3.9 takes a more conservative approach and issues an error on these questionable operations.

ts
function foo<T extends any>(arg: T) {
arg.spfjgerijghoied;
// ~~~~~~~~~~~~~~~
// Property 'spfjgerijghoied' does not exist on type 'T'.
}

export * 总是被保留

🌐 export * is Always Retained

在以前的 TypeScript 版本中,如果 foo 没有导出任何值,那么像 export * from "foo" 这样的声明将在我们的 JavaScript 输出中被删除。这种输出方式存在问题,因为它是类型驱动的,Babel 无法模拟。TypeScript 3.9 将始终输出这些 export * 声明。实际上,我们不认为这会破坏太多现有代码。

🌐 In previous TypeScript versions, declarations like export * from "foo" would be dropped in our JavaScript output if foo didn’t export any values. This sort of emit is problematic because it’s type-directed and can’t be emulated by Babel. TypeScript 3.9 will always emit these export * declarations. In practice, we don’t expect this to break much existing code.

更多 libdom.d.ts 改进

🌐 More libdom.d.ts refinements

我们正在继续将更多 TypeScript 内置的 .d.ts 库(lib.d.ts 及其相关文件)从 DOM 规范中的 Web IDL 文件直接生成。因此,一些与媒体访问相关的供应商特定类型已被移除。

🌐 We are continuing to move more of TypeScript’s built-in .d.ts library (lib.d.ts and family) to be generated from Web IDL files directly from the DOM specification. As a result some vendor-specific types related to media access have been removed.

将此文件添加到项目的环境 *.d.ts 中将会将它们恢复:

🌐 Adding this file to an ambient *.d.ts to your project will bring them back:

ts
interface AudioTrackList {
[Symbol.iterator](): IterableIterator<AudioTrack>;
}
interface HTMLVideoElement {
readonly audioTracks: AudioTrackList
msFrameStep(forward: boolean): void;
msInsertVideoEffect(activatableClassId: string, effectRequired: boolean, config?: any): void;
msSetVideoRectangle(left: number, top: number, right: number, bottom: number): void;
webkitEnterFullScreen(): void;
webkitEnterFullscreen(): void;
webkitExitFullScreen(): void;
webkitExitFullscreen(): void;
msHorizontalMirror: boolean;
readonly msIsLayoutOptimalForPlayback: boolean;
readonly msIsStereo3D: boolean;
msStereo3DPackingMode: string;
msStereo3DRenderMode: string;
msZoom: boolean;
onMSVideoFormatChanged: ((this: HTMLVideoElement, ev: Event) => any) | null;
onMSVideoFrameStepCompleted: ((this: HTMLVideoElement, ev: Event) => any) | null;
onMSVideoOptimalLayoutChanged: ((this: HTMLVideoElement, ev: Event) => any) | null;
webkitDisplayingFullscreen: boolean;
webkitSupportsFullscreen: boolean;
}
interface MediaError {
readonly msExtendedCode: number;
readonly MS_MEDIA_ERR_ENCRYPTED: number;
}