更智能的类型别名保存
🌐 Smarter Type Alias Preservation
TypeScript 有一种为类型声明新名称的方法,称为类型别名。如果你正在编写一组都作用于 string | number | boolean 的函数,你可以编写一个类型别名,以避免重复自己。
🌐 TypeScript has a way to declare new names for types called type aliases.
If you’re writing a set of functions that all work on string | number | boolean, you can write a type alias to avoid repeating yourself over and over again.
tstype BasicPrimitive = number | string | boolean;
TypeScript 一直使用一套规则和推测来决定在打印类型时何时重用类型别名。例如,看看以下代码片段。
🌐 TypeScript has always used a set of rules and guesses for when to reuse type aliases when printing out types. For example, take the following code snippet.
tsexport type BasicPrimitive = number | string | boolean;export function doStuff(value: BasicPrimitive) {let x = value;return x;}
如果我们在像 Visual Studio、Visual Studio Code 或 TypeScript Playground 这样的编辑器中将鼠标悬停在 x 上,我们将看到一个快速信息面板,显示类型为 BasicPrimitive。
同样,如果我们获取此文件的声明文件输出(.d.ts 输出),TypeScript 会说明 doStuff 返回 BasicPrimitive。
🌐 If we hover our mouse over x in an editor like Visual Studio, Visual Studio Code, or the TypeScript Playground, we’ll get a quick info panel that shows the type BasicPrimitive.
Likewise, if we get the declaration file output (.d.ts output) for this file, TypeScript will say that doStuff returns BasicPrimitive.
但是,如果我们返回 BasicPrimitive 或 undefined 会发生什么呢?
🌐 However, what happens if we return a BasicPrimitive or undefined?
tsexport type BasicPrimitive = number | string | boolean;export function doStuff(value: BasicPrimitive) {if (Math.random() < 0.5) {return undefined;}return value;}
我们可以在 TypeScript 4.1 playground 中看到会发生什么。虽然我们可能希望 TypeScript 显示 doStuff 的返回类型为 BasicPrimitive | undefined,但它却显示为 string | number | boolean | undefined!这是怎么回事?
🌐 We can see what happens in the TypeScript 4.1 playground.
While we might want TypeScript to display the return type of doStuff as BasicPrimitive | undefined, it instead displays string | number | boolean | undefined!
What gives?
这与 TypeScript 如何在内部表示类型有关。
当从一个或多个联合类型创建联合类型时,它总会将这些类型 规范化 成一个新的扁平联合类型——但这样做会丢失信息。
类型检查器必须找到 string | number | boolean | undefined 的每一种类型组合,以查看可能使用了哪些类型别名,即便如此,也可能有多个类型别名指向 string | number | boolean。
🌐 Well this has to do with how TypeScript represents types internally.
When creating a union type out of one or more union types, it will always normalize those types into a new flattened union type - but doing that loses information.
The type-checker would have to find every combination of types from string | number | boolean | undefined to see what type aliases could have been used, and even then, there might be multiple type aliases to string | number | boolean.
在 TypeScript 4.2 中,我们的内部机制更智能了一些。我们通过保留类型最初的书写和构建方式的部分信息,来跟踪类型是如何构造的。我们还会跟踪并区分类型别名与其他别名的实例!
🌐 In TypeScript 4.2, our internals are a little smarter. We keep track of how types were constructed by keeping around parts of how they were originally written and constructed over time. We also keep track of, and differentiate, type aliases to instances of other aliases!
能够根据你在代码中使用的方式打印回类型意味着,作为 TypeScript 用户,你可以避免一些不幸的庞大类型被显示出来,而且这通常会转化为更好的 .d.ts 文件输出、错误信息以及编辑器中 Quick Info 和签名帮助的类型显示。这可以让 TypeScript 对新手来说感觉更容易接近。
🌐 Being able to print back the types based on how you used them in your code means that as a TypeScript user, you can avoid some unfortunately humongous types getting displayed, and that often translates to getting better .d.ts file output, error messages, and in-editor type displays in quick info and signature help.
This can help TypeScript feel a little bit more approachable for newcomers.
欲了解更多信息,请查看第一个改进了各种情况下保留联合类型别名的拉取请求,以及第二个保留间接别名的拉取请求。
🌐 For more information, check out the first pull request that improves various cases around preserving union type aliases, along with a second pull request that preserves indirect aliases.
元组类型中的前导/中间剩余元素
🌐 Leading/Middle Rest Elements in Tuple Types
在 TypeScript 中,元组类型旨在模拟具有特定长度和元素类型的数组。
🌐 In TypeScript, tuple types are meant to model arrays with specific lengths and element types.
ts// A tuple that stores a pair of numberslet a: [number, number] = [1, 2];// A tuple that stores a string, a number, and a booleanlet b: [string, number, boolean] = ["hello", 42, true];
随着时间的推移,TypeScript 的元组类型变得越来越复杂,因为它们还用于模拟 JavaScript 中的参数列表等内容。因此,它们可以有可选元素和剩余元素,甚至可以有用于工具支持和可读性的标签。
🌐 Over time, TypeScript’s tuple types have become more and more sophisticated, since they’re also used to model things like parameter lists in JavaScript. As a result, they can have optional elements and rest elements, and can even have labels for tooling and readability.
tsTry// A tuple that has either one or two strings.letc : [string, string?] = ["hello"];c = ["hello", "world"];// A labeled tuple that has either one or two strings.letd : [first : string,second ?: string] = ["hello"];d = ["hello", "world"];// A tuple with a *rest element* - holds at least 2 strings at the front,// and any number of booleans at the back.lete : [string, string, ...boolean[]];e = ["hello", "world"];e = ["hello", "world", false];e = ["hello", "world", true, false, true];
在 TypeScript 4.2 中,剩余元素的使用方式得到了特别扩展。在早期版本中,TypeScript 仅允许将 ...rest 元素放在元组类型的最后一个位置。
🌐 In TypeScript 4.2, rest elements specifically been expanded in how they can be used.
In prior versions, TypeScript only allowed ...rest elements at the very last position of a tuple type.
然而,现在剩余元素可以在元组中的任何位置出现——只有少数几个限制。
🌐 However, now rest elements can occur anywhere within a tuple - with only a few restrictions.
tsTryletfoo : [...string[], number];foo = [123];foo = ["hello", 123];foo = ["hello!", "hello!", "hello!", 123];letbar : [boolean, ...string[], boolean];bar = [true, false];bar = [true, "some text", false];bar = [true, "some", "separated", "text", false];
唯一的限制是,只要后面没有跟其他可选元素或剩余元素,剩余元素可以放在元组的任何位置。换句话说,每个元组只能有一个剩余元素,并且剩余元素之后不能有可选元素。
🌐 The only restriction is that a rest element can be placed anywhere in a tuple, so long as it’s not followed by another optional element or rest element. In other words, only one rest element per tuple, and no optional elements after rest elements.
tsTryinterfaceClown {/*...*/}interfaceJoker {/*...*/}letA rest element cannot follow another rest element.1265A rest element cannot follow another rest element.StealersWheel : [...Clown [], "me", ...Joker []];letAn optional element cannot follow a rest element.1266An optional element cannot follow a rest element.StringsAndMaybeBoolean : [...string[], boolean?];
这些非尾随 rest 元素可用于对接受任意数量的前导参数和后跟几个固定参数的函数进行建模。
🌐 These non-trailing rest elements can be used to model functions that take any number of leading arguments, followed by a few fixed ones.
tsTrydeclare functiondoStuff (...args : [...names : string[],shouldCapitalize : boolean]): void;doStuff (/*shouldCapitalize:*/ false)doStuff ("fee", "fi", "fo", "fum", /*shouldCapitalize:*/ true);
尽管 JavaScript 没有任何语法来表示前置剩余参数,我们仍然可以通过为 ...args 剩余参数声明一个 使用前置剩余元素的元组类型 来将 doStuff 声明为一个接收前置参数的函数。这可以帮助建模大量现有的 JavaScript 代码!
🌐 Even though JavaScript doesn’t have any syntax to model leading rest parameters, we were still able to declare doStuff as a function that takes leading arguments by declaring the ...args rest parameter with a tuple type that uses a leading rest element.
This can help model lots of existing JavaScript out there!
欲了解更多详情,请查看原拉取请求。
🌐 For more details, see the original pull request.
in 操作符的更严格检查
🌐 Stricter Checks For The in Operator
在 JavaScript 中,在 in 运算符的右侧使用非对象类型会导致运行时错误。TypeScript 4.2 可以确保在设计时捕获此类错误。
🌐 In JavaScript, it is a runtime error to use a non-object type on the right side of the in operator.
TypeScript 4.2 ensures this can be caught at design-time.
tsTry"foo" inType 'number' is not assignable to type 'object'.2322Type 'number' is not assignable to type 'object'.42 ;
此检查在大多数情况下都相当保守,因此如果你收到与此相关的错误,则很可能是代码中存在问题。
🌐 This check is fairly conservative for the most part, so if you have received an error about this, it is likely an issue in the code.
非常感谢我们的外部贡献者 Jonas Hübotter 的 拉取请求!
🌐 A big thanks to our external contributor Jonas Hübotter for their pull request!
--noPropertyAccessFromIndexSignature
当 TypeScript 首次引入索引签名时,你只能通过使用“方括号”元素访问语法(例如 person["name"])来获取由它们声明的属性。
🌐 Back when TypeScript first introduced index signatures, you could only get properties declared by them with “bracketed” element access syntax like person["name"].
tsTryinterfaceSomeType {/** This is an index signature. */[propName : string]: any;}functiondoStuff (value :SomeType ) {letx =value ["someProperty"];}
在需要处理具有任意属性的对象的情况下,这最终变得很麻烦。例如,想象一个 API,其中常常因为在属性名称末尾多加了一个 s 字符而拼写错误。
🌐 This ended up being cumbersome in situations where we need to work with objects that have arbitrary properties.
For example, imagine an API where it’s common to misspell a property name by adding an extra s character at the end.
tsTryinterfaceOptions {/** File patterns to be excluded. */exclude ?: string[];/*** It handles any extra properties that we haven't declared as type 'any'.*/[x : string]: any;}functionprocessOptions (opts :Options ) {// Notice we're *intentionally* accessing `excludes`, not `exclude`if (opts .excludes ) {console .error ("The option `excludes` is not valid. Did you mean `exclude`?");}}
为了让这类情况更容易处理,TypeScript 不久前使得当一个类型具有字符串索引签名时,可以使用“点状”属性访问语法,例如 person.name。这也使得将现有的 JavaScript 代码迁移到 TypeScript 变得更加容易。
🌐 To make these types of situations easier, a while back, TypeScript made it possible to use “dotted” property access syntax like person.name when a type had a string index signature.
This also made it easier to transition existing JavaScript code over to TypeScript.
但是,放宽限制也意味着拼写错误显式声明的属性变得更容易。
🌐 However, loosening the restriction also meant that misspelling an explicitly declared property became much easier.
tsTryfunctionprocessOptions (opts :Options ) {// ...// Notice we're *accidentally* accessing `excludes` this time.// Oops! Totally valid.for (constexcludePattern ofopts .excludes ) {// ...}}
在某些情况下,用户更愿意明确选择使用索引签名——当点表示法访问的属性与特定的属性声明不对应时,他们希望得到错误信息。
🌐 In some cases, users would prefer to explicitly opt into the index signature - they would prefer to get an error message when a dotted property access doesn’t correspond to a specific property declaration.
这就是为什么 TypeScript 引入了一个名为 noPropertyAccessFromIndexSignature 的新标志。 在这种模式下,你将被启用 TypeScript 旧的行为,这种行为会发出错误。 这个新设置不属于 strict 系列标志,因为我们认为用户会在某些代码库中觉得它比在其他代码库中更有用。
🌐 That’s why TypeScript introduces a new flag called noPropertyAccessFromIndexSignature.
Under this mode, you’ll be opted in to TypeScript’s older behavior that issues an error.
This new setting is not under the strict family of flags, since we believe users will find it more useful on certain codebases than others.
你可以通过阅读相应的拉取请求来更详细地了解此功能。我们还要特别感谢王文璐向我们提交了这次拉取请求!
🌐 You can understand this feature in more detail by reading up on the corresponding pull request. We’d also like to extend a big thanks to Wenlu Wang who sent us this pull request!
abstract 构造签名
🌐 abstract Construct Signatures
TypeScript 允许我们将一个类标记为 抽象。这告诉 TypeScript 该类仅用于被继承,并且某些成员需要由任何子类填写,才能真正创建实例。
🌐 TypeScript allows us to mark a class as abstract. This tells TypeScript that the class is only meant to be extended from, and that certain members need to be filled in by any subclass to actually create an instance.
tsTryabstract classShape {abstractgetArea (): number;}newCannot create an instance of an abstract class.2511Cannot create an instance of an abstract class.Shape ();classSquare extendsShape {#sideLength: number;constructor(sideLength : number) {super();this.#sideLength =sideLength ;}getArea () {return this.#sideLength ** 2;}}// Works fine.newSquare (42);
为了确保在 new 中对 abstract 类的限制能够始终如一地应用,你不能将 abstract 类分配给任何期望构造签名的地方。
🌐 To make sure this restriction in new-ing up abstract classes is consistently applied, you can’t assign an abstract class to anything that expects a construct signature.
tsTryinterfaceHasArea {getArea (): number;}letType 'typeof Shape' is not assignable to type 'new () => HasArea'. Cannot assign an abstract constructor type to a non-abstract constructor type.2322Type 'typeof Shape' is not assignable to type 'new () => HasArea'. Cannot assign an abstract constructor type to a non-abstract constructor type.: new () => Ctor HasArea =Shape ;
在我们打算运行像 new Ctor 这样的代码时,这样做是正确的,但如果我们想要编写 Ctor 的子类,这样做就过于严格了。
🌐 This does the right thing in case we intend to run code like new Ctor, but it’s overly-restrictive in case we want to write a subclass of Ctor.
tsTryabstract classShape {abstractgetArea (): number;}interfaceHasArea {getArea (): number;}functionmakeSubclassWithArea (Ctor : new () =>HasArea ) {return class extendsCtor {getArea () {return 42}};}letArgument of type 'typeof Shape' is not assignable to parameter of type 'new () => HasArea'. Cannot assign an abstract constructor type to a non-abstract constructor type.2345Argument of type 'typeof Shape' is not assignable to parameter of type 'new () => HasArea'. Cannot assign an abstract constructor type to a non-abstract constructor type.MyShape =makeSubclassWithArea (); Shape
它在像 InstanceType 这样的内置辅助类型上也不太好用。
🌐 It also doesn’t work well with built-in helper types like InstanceType.
tsTrytypeMyInstance =InstanceType <typeofShape >;
这就是为什么 TypeScript 4.2 允许你在构造函数签名上指定 abstract 修饰符的原因。
🌐 That’s why TypeScript 4.2 allows you to specify an abstract modifier on constructor signatures.
tsTryinterfaceHasArea {getArea (): number;}// Works!letCtor : abstract new () =>HasArea =Shape ;
向构造签名添加 abstract 修饰符表示你可以传入 abstract 构造函数。它并不会阻止你传入其他“具体的”类/构造函数——它实际上只是表明没有直接运行构造函数的意图,因此传入任何类类型都是安全的。
🌐 Adding the abstract modifier to a construct signature signals that you can pass in abstract constructors.
It doesn’t stop you from passing in other classes/constructor functions that are “concrete” - it really just signals that there’s no intent to run the constructor directly, so it’s safe to pass in either class type.
此功能允许我们以支持抽象类的方式编写 mixin 工厂。例如,在下面的代码片段中,我们能够将 mixin 函数 withStyles 与 abstract 类 SuperClass 一起使用。
🌐 This feature allows us to write mixin factories in a way that supports abstract classes.
For example, in the following code snippet, we’re able to use the mixin function withStyles with the abstract class SuperClass.
tsTryabstract classSuperClass {abstractsomeMethod (): void;badda () {}}typeAbstractConstructor <T > = abstract new (...args : any[]) =>T functionwithStyles <T extendsAbstractConstructor <object>>(Ctor :T ) {abstract classStyledClass extendsCtor {getStyles () {// ...}}returnStyledClass ;}classSubClass extendswithStyles (SuperClass ) {someMethod () {this.someMethod ()}}
请注意,withStyles 正在演示一个特定规则,其中一个类(如 StyledClass)继承了一个由抽象构造函数(如 Ctor)界定的泛型值,也必须声明为 abstract。这是因为无法确定是否传入了包含更多抽象成员的类,因此无法知道子类是否实现了所有抽象成员。
🌐 Note that withStyles is demonstrating a specific rule, where a class (like StyledClass) that extends a value that’s generic and bounded by an abstract constructor (like Ctor) has to also be declared abstract.
This is because there’s no way to know if a class with more abstract members was passed in, and so it’s impossible to know whether the subclass implements all the abstract members.
你可以在 它的拉取请求 上了解更多关于抽象构造签名的信息。
🌐 You can read up more on abstract construct signatures on its pull request.
使用 --explainFiles 了解你的项目结构
🌐 Understanding Your Project Structure With --explainFiles
对于 TypeScript 用户来说,一个出乎意料的常见情况是会问“为什么 TypeScript 会包含这个文件?”
推断程序的文件是一个复杂的过程,因此有很多原因导致特定的 lib.d.ts 组合被使用,为什么 node_modules 中的某些文件会被包含,以及为什么某些文件会被包含,即使我们本以为指定 exclude 就能将它们排除在外。
🌐 A surprisingly common scenario for TypeScript users is to ask “why is TypeScript including this file?“.
Inferring the files of your program turns out to be a complicated process, and so there are lots of reasons why a specific combination of lib.d.ts got used, why certain files in node_modules are getting included, and why certain files are being included even though we thought specifying exclude would keep them out.
这就是为什么 TypeScript 现在提供了一个 explainFiles 标志。
🌐 That’s why TypeScript now provides an explainFiles flag.
shtsc --explainFiles
使用此选项时,TypeScript 编译器会非常详细地输出文件为何会出现在你的程序中的原因。为了更容易阅读,你可以将输出转发到文件,或者将其管道传输到可以轻松查看的程序中。
🌐 When using this option, the TypeScript compiler will give some very verbose output about why a file ended up in your program. To read it more easily, you can forward the output to a file, or pipe it to a program that can easily view it.
sh# Forward output to a text filetsc --explainFiles > explanation.txt# Pipe output to a utility program like `less`, or an editor like VS Codetsc --explainFiles | lesstsc --explainFiles | code -
通常,输出会首先列出包含 lib.d.ts 文件的原因,然后是本地文件,最后是 node_modules 文件的原因。
🌐 Typically, the output will start out by listing out reasons for including lib.d.ts files, then for local files, and then node_modules files.
TS_Compiler_Directory/4.2.2/lib/lib.es5.d.tsLibrary referenced via 'es5' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2015.d.ts'TS_Compiler_Directory/4.2.2/lib/lib.es2015.d.tsLibrary referenced via 'es2015' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2016.d.ts'TS_Compiler_Directory/4.2.2/lib/lib.es2016.d.tsLibrary referenced via 'es2016' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2017.d.ts'TS_Compiler_Directory/4.2.2/lib/lib.es2017.d.tsLibrary referenced via 'es2017' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2018.d.ts'TS_Compiler_Directory/4.2.2/lib/lib.es2018.d.tsLibrary referenced via 'es2018' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2019.d.ts'TS_Compiler_Directory/4.2.2/lib/lib.es2019.d.tsLibrary referenced via 'es2019' from file 'TS_Compiler_Directory/4.2.2/lib/lib.es2020.d.ts'TS_Compiler_Directory/4.2.2/lib/lib.es2020.d.tsLibrary referenced via 'es2020' from file 'TS_Compiler_Directory/4.2.2/lib/lib.esnext.d.ts'TS_Compiler_Directory/4.2.2/lib/lib.esnext.d.tsLibrary 'lib.esnext.d.ts' specified in compilerOptions... More Library References...foo.tsMatched by include pattern '**/*' in 'tsconfig.json'
目前,我们无法保证输出格式——它可能会随时间变化。
话虽如此,如果你有任何建议,我们很想改进这个格式!
🌐 Right now, we make no guarantees about the output format - it might change over time. On that note, we’re interested in improving this format if you have any suggestions!
欲了解更多信息,请查看原始拉取请求!
🌐 For more information, check out the original pull request!
改进了逻辑表达式中未调用函数的检查
🌐 Improved Uncalled Function Checks in Logical Expressions
多亏了 Alex Tarasyuk 的进一步改进,TypeScript 的未调用函数检查现在也适用于 && 和 || 表达式。
🌐 Thanks to further improvements from Alex Tarasyuk, TypeScript’s uncalled function checks now apply within && and || expressions.
在 strictNullChecks 下,以下代码现在会出错。
🌐 Under strictNullChecks, the following code will now error.
tsfunction shouldDisplayElement(element: Element) {// ...return true;}function getVisibleItems(elements: Element[]) {return elements.filter((e) => shouldDisplayElement && e.children.length);// ~~~~~~~~~~~~~~~~~~~~// This condition will always return true since the function is always defined.// Did you mean to call it instead.}
欲了解更多详情,请查看此处的拉取请求。
🌐 For more details, check out the pull request here.
解构变量可以明确标记为未使用
🌐 Destructured Variables Can Be Explicitly Marked as Unused
多亏了 Alex Tarasyuk 的另一个拉取请求,你现在可以通过在解构变量前加下划线(即 _ 字符)来将其标记为未使用。
🌐 Thanks to another pull request from Alex Tarasyuk, you can now mark destructured variables as unused by prefixing them with an underscore (the _ character).
tslet [_first, second] = getValues();
以前,如果 _first 后续从未使用过,TypeScript 会在 noUnusedLocals 下发出错误。现在,TypeScript 会识别出 _first 被故意命名带下划线,因为没有打算使用它。
🌐 Previously, if _first was never used later on, TypeScript would issue an error under noUnusedLocals.
Now, TypeScript will recognize that _first was intentionally named with an underscore because there was no intent to use it.
欲了解更多详情,请查看完整变更。
🌐 For more details, take a look at the full change.
可选属性和字符串索引签名之间的宽松规则
🌐 Relaxed Rules Between Optional Properties and String Index Signatures
字符串索引签名是一种对类似字典的对象进行类型化的方法,你可以使用任意键进行访问:
🌐 String index signatures are a way of typing dictionary-like objects, where you want to allow access with arbitrary keys:
tsTryconstmovieWatchCount : { [key : string]: number } = {};functionwatchMovie (title : string) {movieWatchCount [title ] = (movieWatchCount [title ] ?? 0) + 1;}
当然,对于字典中尚未存在的任何电影标题,movieWatchCount[title] 将是 undefined(TypeScript 4.1 添加了 noUncheckedIndexedAccess 选项,以便在从这样的索引签名读取时包括 undefined)。
尽管很明显 movieWatchCount 中肯定存在一些字符串,但 TypeScript 的早期版本会将可选对象属性视为无法分配给其他兼容的索引签名,这是由于存在 undefined。
🌐 Of course, for any movie title not yet in the dictionary, movieWatchCount[title] will be undefined (TypeScript 4.1 added the option noUncheckedIndexedAccess to include undefined when reading from an index signature like this).
Even though it’s clear that there must be some strings not present in movieWatchCount, previous versions of TypeScript treated optional object properties as unassignable to otherwise compatible index signatures, due to the presence of undefined.
tsTrytypeWesAndersonWatchCount = {"Fantastic Mr. Fox"?: number;"The Royal Tenenbaums"?: number;"Moonrise Kingdom"?: number;"The Grand Budapest Hotel"?: number;};declare constwesAndersonWatchCount :WesAndersonWatchCount ;constmovieWatchCount : { [key : string]: number } =wesAndersonWatchCount ;// ~~~~~~~~~~~~~~~ error!// Type 'WesAndersonWatchCount' is not assignable to type '{ [key: string]: number; }'.// Property '"Fantastic Mr. Fox"' is incompatible with index signature.// Type 'number | undefined' is not assignable to type 'number'.// Type 'undefined' is not assignable to type 'number'. (2322)
TypeScript 4.2 允许这种赋值。然而,它不允许将类型中带有 undefined 的非可选属性赋值,也不允许将 undefined 写入特定的键:
🌐 TypeScript 4.2 allows this assignment. However, it does not allow the assignment of non-optional properties with undefined in their types, nor does it allow writing undefined to a specific key:
tsTrytypeBatmanWatchCount = {"Batman Begins": number | undefined;"The Dark Knight": number | undefined;"The Dark Knight Rises": number | undefined;};declare constbatmanWatchCount :BatmanWatchCount ;// Still an error in TypeScript 4.2.constType 'BatmanWatchCount' is not assignable to type '{ [key: string]: number; }'. Property '"Batman Begins"' is incompatible with index signature. Type 'number | undefined' is not assignable to type 'number'. Type 'undefined' is not assignable to type 'number'.2322Type 'BatmanWatchCount' is not assignable to type '{ [key: string]: number; }'. Property '"Batman Begins"' is incompatible with index signature. Type 'number | undefined' is not assignable to type 'number'. Type 'undefined' is not assignable to type 'number'.: { [ movieWatchCount key : string]: number } =batmanWatchCount ;// Still an error in TypeScript 4.2.// Index signatures don't implicitly allow explicit `undefined`.Type 'undefined' is not assignable to type 'number'.2322Type 'undefined' is not assignable to type 'number'.movieWatchCount ["It's the Great Pumpkin, Charlie Brown"] =undefined ;
新规则也不适用于数字索引签名,因为它们被假定为类似数组且密集:
🌐 The new rule also does not apply to number index signatures, since they are assumed to be array-like and dense:
tsTrydeclare letsortOfArrayish : { [key : number]: string };declare letnumberKeys : { 42?: string };Type '{ 42?: string | undefined; }' is not assignable to type '{ [key: number]: string; }'. Property '42' is incompatible with index signature. Type 'string | undefined' is not assignable to type 'string'. Type 'undefined' is not assignable to type 'string'.2322Type '{ 42?: string | undefined; }' is not assignable to type '{ [key: number]: string; }'. Property '42' is incompatible with index signature. Type 'string | undefined' is not assignable to type 'string'. Type 'undefined' is not assignable to type 'string'.= sortOfArrayish numberKeys ;
你可以通过阅读原始的 PR 来更好地了解这一变化。
🌐 You can get a better sense of this change by reading up on the original PR.
声明缺失的辅助函数
🌐 Declare Missing Helper Function
感谢 Alexander Tarasyuk 的 社区拉取请求,我们现在有了一个快速修复方法,可以根据调用位置声明新的函数和方法!
🌐 Thanks to a community pull request from Alexander Tarasyuk, we now have a quick fix for declaring new functions and methods based on the call-site!

打破变更
🌐 Breaking Changes
我们始终努力在版本发布中将破坏性更改降到最低。TypeScript 4.2 包含一些破坏性更改,但我们相信在升级时应该是可管理的。
🌐 We always strive to minimize breaking changes in a release. TypeScript 4.2 contains some breaking changes, but we believe they should be manageable in an upgrade.
lib.d.ts 更新
🌐 lib.d.ts Updates
与每个 TypeScript 版本一样,lib.d.ts 的声明(特别是为 web 环境生成的声明)已经发生了变化。虽然有各种变化,但 Intl 和 ResizeObserver 的变化可能是最具破坏性的。
🌐 As with every TypeScript version, declarations for lib.d.ts (especially the declarations generated for web contexts), have changed.
There are various changes, though Intl and ResizeObserver’s may end up being the most disruptive.
noImplicitAny 错误适用于松散的 yield 表达式
🌐 noImplicitAny Errors Apply to Loose yield Expressions
当 yield 表达式的值被捕获,但 TypeScript 无法立即确定你希望它接收的类型(即 yield 表达式没有上下文类型)时,TypeScript 现在将发出一个隐式 any 错误。
🌐 When the value of a yield expression is captured, but TypeScript can’t immediately figure out what type you intend for it to receive (i.e. the yield expression isn’t contextually typed), TypeScript will now issue an implicit any error.
tsTryfunction*g1 () {const'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation.7057'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation.value =yield 1;}function*g2 () {// No error.// The result of `yield 1` is unused.yield 1;}function*g3 () {// No error.// `yield 1` is contextually typed by 'string'.constvalue : string = yield 1;}function*g4 ():Generator <number, void, string> {// No error.// TypeScript can figure out the type of `yield 1`// from the explicit return type of `g4`.constvalue = yield 1;}
请参阅相应的更改了解更多详情。
🌐 See more details in the corresponding changes.
扩展的未调用函数检查
🌐 Expanded Uncalled Function Checks
如上所述,在使用 strictNullChecks 时,未调用的函数检查现在将在 && 和 || 表达式中一致地运行。这可能会导致新的中断,但通常表示现有代码中的逻辑错误。
🌐 As described above, uncalled function checks will now operate consistently within && and || expressions when using strictNullChecks.
This can be a source of new breaks, but is typically an indication of a logic error in existing code.
JavaScript 中的类型参数不会被解析为类型参数
🌐 Type Arguments in JavaScript Are Not Parsed as Type Arguments
在 JavaScript 中类型参数本来就不被允许,但在 TypeScript 4.2 中,解析器将以更符合规范的方式解析它们。 因此在 JavaScript 文件中编写以下代码时:
🌐 Type arguments were already not allowed in JavaScript, but in TypeScript 4.2, the parser will parse them in a more spec-compliant way. So when writing the following code in a JavaScript file:
tsf<T>(100);
TypeScript 将按以下 JavaScript 代码进行解析:
🌐 TypeScript will parse it as the following JavaScript:
jsf < T > 100;
如果你正在利用 TypeScript 的 API 解析 JavaScript 文件中的类型构造,这可能会对你造成影响,这种情况可能在尝试解析 Flow 文件时发生。
🌐 This may impact you if you were leveraging TypeScript’s API to parse type constructs in JavaScript files, which may have occurred when trying to parse Flow files.
请参阅 拉取请求 了解检查了哪些内容的更多细节。
🌐 See the pull request for more details on what’s checked.
展开的元组大小限制
🌐 Tuple size limits for spreads
在 TypeScript 中,可以使用任何类型的扩展语法(...)来创建元组类型。
🌐 Tuple types can be made by using any sort of spread syntax (...) in TypeScript.
ts// Tuple types with spread elementstype NumStr = [number, string];type NumStrNumStr = [...NumStr, ...NumStr];// Array spread expressionsconst numStr = [123, "hello"] as const;const numStrNumStr = [...numStr, ...numStr] as const;
有时候,这些元组类型可能不小心变得非常庞大,这会导致类型检查花费很长时间。为了避免让类型检查过程挂起(在编辑器场景中尤其糟糕),TypeScript 设置了一个限制器,以避免进行所有这些工作。
🌐 Sometimes these tuple types can accidentally grow to be huge, and that can make type-checking take a long time. Instead of letting the type-checking process hang (which is especially bad in editor scenarios), TypeScript has a limiter in place to avoid doing all that work.
你可以查看此 拉取请求 以获取更多详细信息。
🌐 You can see this pull request for more details.
.d.ts 扩展名无法用于导入路径
🌐 .d.ts Extensions Cannot Be Used In Import Paths
在 TypeScript 4.2 中,如果你的导入路径在扩展名中包含 .d.ts,将会报错。
🌐 In TypeScript 4.2, it is now an error for your import paths to contain .d.ts in the extension.
ts// must be changed to something like// - "./foo"// - "./foo.js"import { Foo } from "./foo.d.ts";
相反,你的导入路径应该反映你的加载器在运行时的处理方式。以下任何一种导入方式可能都可以使用。
🌐 Instead, your import paths should reflect whatever your loader will do at runtime. Any of the following imports might be usable instead.
tsimport { Foo } from "./foo";import { Foo } from "./foo.js";import { Foo } from "./foo/index.js";
恢复模板字面量推断
🌐 Reverting Template Literal Inference
此更改从 TypeScript 4.2 测试版中移除了一个功能。 如果你还没有升级到我们上一个稳定版本,你不会受到影响,但你可能仍然会对该更改感兴趣。
🌐 This change removed a feature from TypeScript 4.2 beta. If you haven’t yet upgraded past our last stable release, you won’t be affected, but you may still be interested in the change.
TypeScript 4.2 的测试版包含了对模板字符串推断的更改。在此更改中,模板字符串字面量要么会被赋予模板字符串类型,要么简化为多个字符串字面量类型。这些类型在赋值给可变变量时会扩展为 string。
🌐 The beta version of TypeScript 4.2 included a change in inference to template strings.
In this change, template string literals would either be given template string types or simplify to multiple string literal types.
These types would then widen to string when assigning to mutable variables.
tsdeclare const yourName: string;// 'bar' is constant.// It has type '`hello ${string}`'.const bar = `hello ${yourName}`;// 'baz' is mutable.// It has type 'string'.let baz = `hello ${yourName}`;
这与字符串字面量推断的工作原理类似。
🌐 This is similar to how string literal inference works.
ts// 'bar' has type '"hello"'.const bar = "hello";// 'baz' has type 'string'.let baz = "hello";
因此,我们认为让模板字符串表达式拥有模板字符串类型会是“统一”的;然而,根据我们所见所闻,这并不总是理想的。
🌐 For that reason, we believed that making template string expressions have template string types would be “consistent”; however, from what we’ve seen and heard, that isn’t always desirable.
作为回应,我们已经恢复了此功能(以及可能导致问题的更改)。
如果你确实希望模板字符串表达式被赋予类似字面量的类型,你总是可以在它的末尾添加 as const。
🌐 In response, we’ve reverted this feature (and potential breaking change).
If you do want a template string expression to be given a literal-like type, you can always add as const to the end of it.
tsdeclare const yourName: string;// 'bar' has type '`hello ${string}`'.const bar = `hello ${yourName}` as const;// ^^^^^^^^// 'baz' has type 'string'.const baz = `hello ${yourName}`;
TypeScript 的 visitNode 中的 lift 回调使用了不同的类型
🌐 TypeScript’s lift Callback in visitNode Uses a Different Type
TypeScript 有一个 visitNode 函数,它接受一个 lift 函数。
lift 现在期望一个 readonly Node[] 而不是 NodeArray<Node>。
从技术上讲,这是一个 API 破坏性更改,你可以在 这里 阅读更多信息。
🌐 TypeScript has a visitNode function that takes a lift function.
lift now expects a readonly Node[] instead of a NodeArray<Node>.
This is technically an API breaking change which you can read more on here.