在 super() 之前允许在构造函数中使用代码
🌐 Allowing Code in Constructors Before super()
在 JavaScript 类中,在引用 this 之前必须调用 super()。TypeScript 也会强制执行这一点,尽管它在确保这一点的方式上有些过于严格。在 TypeScript 中,如果类中包含任何属性初始化器,则在构造函数开头包含任何代码以前都是错误的。
🌐 In JavaScript classes it’s mandatory to call super() before referring to this.
TypeScript enforces this as well, though it was a bit too strict in how it ensured this.
In TypeScript, it was previously an error to contain any code at the beginning of a constructor if its containing class had any property initializers.
tsclass Base {// ...}class Derived extends Base {someProperty = true;constructor() {// error!// have to call 'super()' first because it needs to initialize 'someProperty'.doSomeStuff();super();}}
这使得检查 super() 在引用 this 之前被调用变得很便宜,但最终它拒绝了很多有效代码。TypeScript 4.6 在该检查上现在宽松得多,并且允许其他代码在 super() 之前运行,同时仍确保 super() 在对 this 的任何引用之前发生于顶层。
🌐 This made it cheap to check that super() gets called before this is referenced, but it ended up rejecting a lot of valid code.
TypeScript 4.6 is now much more lenient in that check and permits other code to run before super()., all while still ensuring that super() occurs at the top-level before any references to this.
我们想向 Joshua Goldberg 表示感谢,感谢他 耐心地与我们合作实现这一变更!
🌐 We’d like to extend our thanks to Joshua Goldberg for patiently working with us to land this change!
用于解构判别联合的控制流分析
🌐 Control Flow Analysis for Destructured Discriminated Unions
TypeScript 能够根据所谓的判别属性来缩小类型。例如,在下面的代码片段中,TypeScript 能够根据每次我们检查 kind 的值时来缩小 action 的类型。
🌐 TypeScript is able to narrow types based on what’s called a discriminant property.
For example, in the following code snippet, TypeScript is able to narrow the type of action based on every time we check against the value of kind.
tstype Action =| { kind: "NumberContents"; payload: number }| { kind: "StringContents"; payload: string };function processAction(action: Action) {if (action.kind === "NumberContents") {// `action.payload` is a number here.let num = action.payload * 2;// ...} else if (action.kind === "StringContents") {// `action.payload` is a string here.const str = action.payload.trim();// ...}}
这让我们能够处理可以保存不同数据的对象,但一个公共字段告诉我们这些对象拥有哪种数据。
🌐 This lets us work with objects that can hold different data, but a common field tells us which data those objects have.
在 TypeScript 中这种情况非常常见;然而,根据你的偏好,你可能想在上面的示例中解构 kind 和 payload。也许可以像下面这样做:
🌐 This is very common in TypeScript; however, depending on your preferences, you might have wanted to destructure kind and payload in the example above.
Perhaps something like the following:
tstype Action =| { kind: "NumberContents"; payload: number }| { kind: "StringContents"; payload: string };function processAction(action: Action) {const { kind, payload } = action;if (kind === "NumberContents") {let num = payload * 2;// ...} else if (kind === "StringContents") {const str = payload.trim();// ...}}
以前 TypeScript 在这些情况下会报错——一旦 kind 和 payload 从同一个对象中提取到变量,它们就被认为是完全独立的。
🌐 Previously TypeScript would error on these - once kind and payload were extracted from the same object into variables, they were considered totally independent.
在 TypeScript 4.6 中,这完全可以正常工作!
🌐 In TypeScript 4.6, this just works!
当将单个属性解构到 const 声明中,或当将参数解构到从未被赋值的变量中时,TypeScript 会检查解构的类型是否为可区分的联合类型。 如果是,TypeScript 现在可以根据对其他变量的检查来缩小变量的类型。 因此,在我们的示例中,对 kind 的检查会缩小 payload 的类型。
🌐 When destructuring individual properties into a const declaration, or when destructuring a parameter into variables that are never assigned to, TypeScript will check for if the destructured type is a discriminated union.
If it is, TypeScript can now narrow the types of variables depending on checks of other variables
So in our example, a check on kind narrows the type of payload.
欲了解更多信息,请参阅实现此分析的拉取请求。
🌐 For more information, see the pull request that implemented this analysis.
改进的递归深度检查
🌐 Improved Recursion Depth Checks
由于 TypeScript 构建于一个也提供泛型的结构化类型系统,因此它面临着一些有趣的挑战。
🌐 TypeScript has some interesting challenges due to the fact that it’s built on a structural type system that also provides generics.
在结构类型系统中,对象类型基于其所拥有的成员而兼容。
🌐 In a structural type system, object types are compatible based on the members they have.
tsinterface Source {prop: string;}interface Target {prop: number;}function check(source: Source, target: Target) {target = source;// error!// Type 'Source' is not assignable to type 'Target'.// Types of property 'prop' are incompatible.// Type 'string' is not assignable to type 'number'.}
注意,Source 是否与 Target 兼容取决于它们的 属性 是否可以赋值。在这种情况下,仅仅是 prop。
🌐 Notice that whether or not Source is compatible with Target has to do with whether their properties are assignable.
In this case, that’s just prop.
当你在这里引入泛型时,会出现一些更难回答的问题。例如,在以下情况下,Source<string> 是否可以赋值给 Target<number>?
🌐 When you introduce generics into this, there are some harder questions to answer.
For instance, is a Source<string> assignable to a Target<number> in the following case?
tsinterface Source<T> {prop: Source<Source<T>>;}interface Target<T> {prop: Target<Target<T>>;}function check(source: Source<string>, target: Target<number>) {target = source;}
为了回答这个问题,TypeScript 需要检查 prop 的类型是否兼容。 这又引出了另一个问题:Source<Source<string>> 是否可以赋值给 Target<Target<number>>? 为了回答这个问题,TypeScript 会检查 prop 是否与这些类型兼容,最终会检查 Source<Source<Source<string>>> 是否可以赋值给 Target<Target<Target<number>>>。 继续深入下去,你可能会注意到类型会随着深入而无限扩展。
TypeScript 在这里有一些启发式方法——如果在遇到某个深度检查后,一个类型 看起来 无限扩展,那么它会认为这些类型 可能 兼容。通常这样就够了,但令人尴尬的是,仍然有一些它无法捕捉到的假否定情况。
🌐 TypeScript has a few heuristics here - if a type appears to be infinitely expanding after encountering a certain depth check, then it considers that the types could be compatible. This is usually enough, but embarrassingly there were some false-negatives that this wouldn’t catch.
tsinterface Foo<T> {prop: T;}declare let x: Foo<Foo<Foo<Foo<Foo<Foo<string>>>>>>;declare let y: Foo<Foo<Foo<Foo<Foo<string>>>>>;x = y;
人类读者可以看出,在上述示例中 x 和 y 应该是不兼容的。虽然这些类型是深度嵌套的,但那只是它们声明方式的结果。这个启发式方法旨在捕捉通过探索类型生成的深度嵌套类型的情况,而不是开发者自己写出的类型。
🌐 A human reader can see that x and y should be incompatible in the above example.
While the types are deeply nested, that’s just a consequence of how they were declared.
The heuristic was meant to capture cases where deeply-nested types were generated through exploring the types, not from when a developer wrote that type out themselves.
TypeScript 4.6 现在能够区分这些情况,并且在最后一个例子中正确报错。此外,由于语言不再担心显式编写类型的误报,TypeScript 可以更早地判断出某个类型会无限扩展,从而在检查类型兼容性时节省大量工作。因此,DefinitelyTyped 上的库如 redux-immutable、react-lazylog 和 yup 的检查时间减少了 50%。
🌐 TypeScript 4.6 is now able to distinguish these cases, and correctly errors on the last example.
Additionally, because the language is no longer concerned with false-positives from explicitly-written types, TypeScript can conclude that a type is infinitely expanding much earlier, and save a bunch of work in checking for type compatibility.
As a result, libraries on DefinitelyTyped like redux-immutable, react-lazylog, and yup saw a 50% reduction in check-time.
你可能已经拥有此更改,因为它已被挑选合并到 TypeScript 4.5.3 中,但这是 TypeScript 4.6 的一个显著特性,你可以在 这里 阅读更多相关内容。
🌐 You may already have this change because it was cherry-picked into TypeScript 4.5.3, but it is a notable feature of TypeScript 4.6 which you can read up more about here.
索引访问推断改进
🌐 Indexed Access Inference Improvements
TypeScript 现在可以正确推断索引访问类型,这些类型会立即索引到映射的对象类型中。
🌐 TypeScript now can correctly infer to indexed access types which immediately index into a mapped object type.
tsinterface TypeMap {number: number;string: string;boolean: boolean;}type UnionRecord<P extends keyof TypeMap> = {[K in P]: {kind: K;v: TypeMap[K];f: (p: TypeMap[K]) => void;};}[P];function processRecord<K extends keyof TypeMap>(record: UnionRecord<K>) {record.f(record.v);}// This call used to have issues - now works!processRecord({kind: "string",v: "hello!",// 'val' used to implicitly have the type 'string | number | boolean',// but now is correctly inferred to just 'string'.f: (val) => {console.log(val.toUpperCase());},});
这个模式已经被支持,并允许 TypeScript 理解对 record.f(record.v) 的调用是有效的,但之前对 processRecord 的调用会导致对 val 的推断结果不佳
🌐 This pattern was already supported and allowed TypeScript to understand that the call to record.f(record.v) is valid, but previously the call to processRecord would give poor inference results for val
TypeScript 4.6 改进了这一点,使得在调用 processRecord 时无需进行类型断言。
🌐 TypeScript 4.6 improves this so that no type assertions are necessary within the call to processRecord.
欲了解更多信息,你可以查看拉取请求。
🌐 For more information, you can read up on the pull request.
依赖参数的控制流分析
🌐 Control Flow Analysis for Dependent Parameters
可以使用类型为元组可区分并集的剩余参数声明签名。
🌐 A signature can be declared with a rest parameter whose type is a discriminated union of tuples.
tsfunction func(...args: ["str", string] | ["num", number]) {// ...}
这段话的意思是,func 的参数完全取决于第一个参数。当第一个参数是字符串 "str" 时,它的第二个参数必须是 string。当第一个参数是字符串 "num" 时,它的第二个参数必须是 number。
🌐 What this says is that the arguments to func depends entirely on the first argument.
When the first argument is the string "str", then its second argument has to be a string.
When its first argument is the string "num", its second argument has to be a number.
如果 TypeScript 可以通过类似这样的签名推断函数类型,那么 TypeScript 现在可以缩小相互依赖的参数范围。
🌐 In cases where TypeScript infers the type of a function from a signature like this, TypeScript can now narrow parameters that depend on each other.
tstype Func = (...args: ["a", number] | ["b", string]) => void;const f1: Func = (kind, payload) => {if (kind === "a") {payload.toFixed(); // 'payload' narrowed to 'number'}if (kind === "b") {payload.toUpperCase(); // 'payload' narrowed to 'string'}};f1("a", 42);f1("b", "hello");
欲了解更多信息,请查看 GitHub 上的更改。
🌐 For more information, see the change on GitHub.
--target es2022
TypeScript 的 --target 选项现在支持 es2022。
这意味着像类字段这样的特性现在有了一个可以保留它们的稳定输出目标。
这也意味着像 Array 的 at() 方法、Object.hasOwn 或 new Error 的 cause 选项 这样的新内置功能,可以使用这个新的 --target 设置,或者使用 --lib es2022。
🌐 TypeScript’s --target option now supports es2022.
This means features like class fields now have a stable output target where they can be preserved.
It also means that new built-in functionality like the at() method on Arrays, Object.hasOwn, or the cause option on new Error can be used either with this new --target setting, or with --lib es2022.
这个功能是由 Kagami Sascha Rosylight (saschanaz) 通过多个 PR 实现 的,我们对这一贡献表示感谢!
🌐 This functionality was implemented by Kagami Sascha Rosylight (saschanaz) over several PRs, and we’re grateful for that contribution!
在 react-jsx 中移除了不必要的参数
🌐 Removed Unnecessary Arguments in react-jsx
以前,在 --jsx react-jsx 中编译如下代码时
🌐 Previously, when compiling code like the following in --jsx react-jsx
tsxexport const el = <div>foo</div>;
TypeScript 将生成以下 JavaScript 代码:
🌐 TypeScript would produce the following JavaScript code:
jsximport { jsx as _jsx } from "react/jsx-runtime";export const el = _jsx("div", { children: "foo" }, void 0);
在这种触发模式下,最后的 void 0 参数是不必要的,移除它可以减小打包体积。
🌐 That last void 0 argument is unnecessary in this emit mode, and removing it can improve bundle sizes.
diff- export const el = _jsx("div", { children: "foo" }, void 0);+ export const el = _jsx("div", { children: "foo" });
多亏了 Alexander Tarasyuk 的 拉取请求,TypeScript 4.6 现在不再使用 void 0 参数。
🌐 Thanks to a pull request from Alexander Tarasyuk, TypeScript 4.6 now drops the void 0 argument.
JSDoc 名称建议
🌐 JSDoc Name Suggestions
在 JSDoc 中,你可以使用 @param 标签来记录参数。
🌐 In JSDoc, you can document parameters using an @param tag.
js/*** @param x The first operand* @param y The second operand*/function add(x, y) {return x + y;}
但是当这些注释过时时会发生什么呢?如果我们将 x 和 y 重命名为 a 和 b 会怎么样?
🌐 But what happens when these comments fall out of date?
What if we rename x and y to a and b?
js/*** @param x {number} The first operand* @param y {number} The second operand*/function add(a, b) {return a + b;}
以前 TypeScript 只有在对 JavaScript 文件进行类型检查时才会告诉你这一点——无论是使用 checkJs 选项,还是在文件顶部添加 // @ts-check 注释。
🌐 Previously TypeScript would only tell you about this when performing type-checking on JavaScript files - when using either the checkJs option, or adding a // @ts-check comment to the top of your file.
现在你可以在编辑器中获取 TypeScript 文件的类似信息! 当函数的参数名称与其 JSDoc 注释不匹配时,TypeScript 现在会提供建议。
🌐 You can now get similar information for TypeScript files in your editor! TypeScript now provides suggestions for when parameter names don’t match between your function and its JSDoc comment.

此更改 由 Alexander Tarasyuk 提供!
JavaScript中的更多语法和绑定错误
🌐 More Syntax and Binding Errors in JavaScript
TypeScript 已经扩展了在 JavaScript 文件中的语法和绑定错误集合。 如果你在像 Visual Studio 或 Visual Studio Code 这样的编辑器中打开 JavaScript 文件,或者通过 TypeScript 编译器运行 JavaScript 代码,即使你没有启用 checkJs 或在文件顶部添加 // @ts-check 注释,你也会看到这些新的错误。
🌐 TypeScript has expanded its set of syntax and binding errors in JavaScript files.
You’ll see these new errors if you open JavaScript files in an editor like Visual Studio or Visual Studio Code, or if you run JavaScript code through the TypeScript compiler - even if you don’t turn on checkJs or add a // @ts-check comment to the top of your files.
举个例子,如果在同一个 JavaScript 文件的作用域中有两个 const 的声明,TypeScript 现在会对这些声明发出错误提示。
🌐 As one example, if you have two declarations of a const in the same scope of a JavaScript file, TypeScript will now issue an error on those declarations.
tsconst foo = 1234;// ~~~// error: Cannot redeclare block-scoped variable 'foo'.// ...const foo = 5678;// ~~~// error: Cannot redeclare block-scoped variable 'foo'.
再例如,如果修饰符使用不正确,TypeScript 会提示你。
🌐 As another example, TypeScript will let you know if a modifier is being incorrectly used.
tsfunction container() {export function foo() {// ~~~~~~// error: Modifiers cannot appear here.}}
可以通过在文件顶部添加 // @ts-nocheck 来禁用这些错误,但我们希望听听你关于它在 JavaScript 工作流中表现的早期反馈。你可以通过安装 TypeScript 和 JavaScript 夜间版扩展 来轻松在 Visual Studio Code 中尝试,并阅读关于 第一个 和 第二个 拉取请求的更多内容。
🌐 These errors can be disabled by adding a // @ts-nocheck at the top of your file, but we’re interested in hearing some early feedback about how it works for your JavaScript workflow.
You can easily try it out for Visual Studio Code by installing the TypeScript and JavaScript Nightly Extension, and read up more on the first and second pull requests.
TypeScript 跟踪分析器
🌐 TypeScript Trace Analyzer
有时,团队可能会遇到一些创建和与其他类型比较计算开销很大的类型。TypeScript 有一个 --generateTrace 标志 可以帮助识别其中一些开销较大的类型,或者有时帮助诊断 TypeScript 编译器中的问题。虽然 --generateTrace 生成的信息可能很有用(尤其是在 TypeScript 4.6 中增加了一些信息的情况下),但在现有的跟踪可视化工具中,它通常很难阅读。
🌐 Occasionally, teams may encounter types that are computationally expensive to create and compare against other types.
TypeScript has a --generateTrace flag to help identify some of those expensive types, or sometimes help diagnose issues in the TypeScript compiler.
While the information generated by --generateTrace can be useful (especially with some information added in TypeScript 4.6), it can often be hard to read in existing trace visualizers.
我们最近发布了一个名为 @typescript/analyze-trace 的工具,用于更直观地查看这些信息。虽然我们不指望每个人都需要 analyze-trace,但我们认为对于遇到 TypeScript 构建性能问题 的团队来说,这个工具可能会很有用。
🌐 We recently published a tool called @typescript/analyze-trace to get a more digestible view of this information.
While we don’t expect everyone to need analyze-trace, we think it can come in handy for any team that is running into build performance issues with TypeScript.
欲了解更多信息,请参阅 analyze-trace 工具的代码库。
🌐 For more information, see the analyze-trace tool’s repo.
打破变更
🌐 Breaking Changes
对象剩余属性会从通用对象中剔除不可展开的成员
🌐 Object Rests Drop Unspreadable Members from Generic Objects
对象剩余表达式现在会删除那些在通用对象上似乎无法展开的成员。 在以下示例中…
🌐 Object rest expressions now drop members that appear to be unspreadable on generic objects. In the following example…
tsclass Thing {someProperty = 42;someMethod() {// ...}}function foo<T extends Thing>(x: T) {let { someProperty, ...rest } = x;// Used to work, is now an error!// Property 'someMethod' does not exist on type 'Omit<T, "someProperty" | "someMethod">'.rest.someMethod();}
变量 rest 过去的类型是 Omit<T, "someProperty">,因为 TypeScript 会严格分析解构了哪些其他属性。
这并不能模拟从非泛型类型解构时 ...rest 的工作方式,因为 someMethod 通常也会被丢弃。
在 TypeScript 4.6 中,rest 的类型是 Omit<T, "someProperty" | "someMethod">。
🌐 the variable rest used to have the type Omit<T, "someProperty"> because TypeScript would strictly analyze which other properties were destructured.
This doesn’t model how ...rest would work in a destructuring from a non-generic type because someMethod would typically be dropped as well.
In TypeScript 4.6, the type of rest is Omit<T, "someProperty" | "someMethod">.
这也可能出现在从 this 进行解构的情况下。当使用 ...rest 元素解构 this 时,不可展开的和非公开的成员现在会被丢弃,这与在其他地方解构类的实例是一致的。
🌐 This can also come up in cases when destructuring from this.
When destructuring this using a ...rest element, unspreadable and non-public members are now dropped, which is consistent with destructuring instances of a class in other places.
tsclass Thing {someProperty = 42;someMethod() {// ...}someOtherMethod() {let { someProperty, ...rest } = this;// Used to work, is now an error!// Property 'someMethod' does not exist on type 'Omit<T, "someProperty" | "someMethod">'.rest.someMethod();}}
欲了解更多详情,请查看此处的相应更改。
🌐 For more details, see the corresponding change here.
JavaScript 文件总是收到语法和绑定错误
🌐 JavaScript Files Always Receive Grammar and Binding Errors
以前,TypeScript 会忽略 JavaScript 中的大多数语法错误,除了在 JavaScript 文件中意外使用 TypeScript 语法的情况。TypeScript 现在会在你的文件中显示 JavaScript 语法和绑定错误,例如使用错误的修饰符、重复声明等。这些错误通常在 Visual Studio Code 或 Visual Studio 中最明显,但在通过 TypeScript 编译器运行 JavaScript 代码时也可能出现。
🌐 Previously, TypeScript would ignore most grammar errors in JavaScript apart from accidentally using TypeScript syntax in a JavaScript file. TypeScript now shows JavaScript syntax and binding errors in your file, such as using incorrect modifiers, duplicate declarations, and more. These will typically be most apparent in Visual Studio Code or Visual Studio, but can also occur when running JavaScript code through the TypeScript compiler.
你可以通过在文件顶部插入 // @ts-nocheck 注释来显式关闭这些错误。
🌐 You can explicitly turn these errors off by inserting a // @ts-nocheck comment at the top of your file.
欲了解更多信息,请参阅这些功能的 第一个 和 第二个 实现拉取请求。
🌐 For more information, see the first and second implementing pull requests for these features.