允许在 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.
ts
class Base {// ...}class Derived extends Base {someProperty = true;constructor() {// error!// have to call 'super()' first because it needs to initialize 'someProperty'.doSomeStuff();super();}}
这使得在引用 this
之前检查 super()
是否被调用变得简单,但最终拒绝了大量有效代码。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
.
ts
type 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:
ts
type 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.
ts
interface 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?
ts
interface 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>>>
。继续深入下去,你可能会注意到,类型会随着深入而无限扩展。
¥In order to answer that, TypeScript needs to check whether the types of prop
are compatible.
That leads to the another question: is a Source<Source<string>>
assignable to a Target<Target<number>>
?
To answer that, TypeScript checks whether prop
is compatible for those types, and ends up checking whether Source<Source<Source<string>>>
is assignable to Target<Target<Target<number>>>
.
Keep going for a bit, and you might notice that the type infinitely expands the more you dig in.
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.
ts
interface 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.
ts
interface 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.
ts
function 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.
ts
type 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
。这意味着像类字段这样的功能现在有了稳定的输出目标,可以在其中保存它们。这也意味着新的内置功能(如 at()
Array
上的方法、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 Array
s, 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 中提出的 implemented,我们非常感谢你的贡献!
¥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
tsx
export const el = <div>foo</div>;
TypeScript 将生成以下 JavaScript 代码:
¥TypeScript would produce the following JavaScript code:
jsx
import { 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 文件的类似信息!TypeScript 现在会在函数与其 JSDoc 注释之间的参数名称不匹配时提供建议。
¥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 提供!
¥This change was provided courtesy of 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.
ts
const 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.
ts
function container() {export function foo() {// ~~~~~~// error: Modifiers cannot appear here.}}
你可以通过在文件顶部添加 // @ts-nocheck
来禁用这些错误,但我们很想听听你关于它如何应用于你的 JavaScript 工作流程的早期反馈。你可以通过安装 TypeScript 和 JavaScript 夜间扩展 轻松地在 Visual Studio Code 中试用它,并阅读更多关于 first 和 second 拉取请求的信息。
¥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…
ts
class 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.
ts
class 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.
更多信息,请参阅 first 和 second 针对这些功能的拉取请求。
¥For more information, see the first and second implementing pull requests for these features.