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%。总的来说,我们相信我们已经将 Material-UI 的编译时间缩短了约 40%!

¥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

想象一下,我们正在导出一个名为 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。更多信息,你可以查看 ts-expect-error 拉取请求

¥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,并且你可能想知道哪种方式更适合未来的代码。虽然完全取决于你和你的团队,但我们对在某些情况下选择哪些 API 有一些想法。

¥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:

  • 你正在编写测试代码,而你实际上希望类型系统在操作时出错。

    ¥you’re writing test code where you actually want the type system to error on an operation

  • 你希望修复程序能够很快完成,并且你只需要一个快速的解决方法。

    ¥you expect a fix to be coming in fairly quickly and you just need a quick workaround

  • 你正在一个规模适中的项目中,并且有一个积极主动的团队,他们希望在受影响的代码恢复有效后立即删除抑制注释。

    ¥you’re in a reasonably-sized project with a proactive team that wants to remove suppression comments as soon as affected code is valid again

如果满足以下条件,请选择 ts-ignore

¥Pick ts-ignore if:

  • 你的项目规模较大,代码中出现了新的错误,但错误原因不明确。

    ¥you have a larger project and new errors have appeared in code with no clear owner

  • 你正在升级两个不同版本的 TypeScript,并且一行代码在一个版本中出现错误,而在另一个版本中没有错误。

    ¥you are in the middle of an upgrade between two different versions of TypeScript, and a line of code errors in one version but not another.

  • 你真的没有时间决定哪个选项更好。

    ¥you honestly don’t have the time to decide which of these options is better.

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

¥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

编辑器需要确定文件属于哪个配置文件,以便应用适当的选项,并确定当前 “project” 中包含哪些其他文件。默认情况下,基于 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" }
]
}

在某些环境中,这个除了管理其他项目文件之外什么都不做的文件通常被称为 “solution”。这里,这些 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;

在上面的代码中,括号会阻止可选链式调用 “short-circuiting” 的行为,因此如果 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 团队以及大多数向我们提供反馈的用户都认为此行为是错误的。我们也一样!我们听到最多的说法是,! 运算符应该只是 “disappear”,因为其目的是从 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;

foo 等于 undefined 时,它只会计算为 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" 描述了一组可能存在的值。

¥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;
}