别名条件和判别式的控制流分析
¥Control Flow Analysis of Aliased Conditions and Discriminants
在 JavaScript 中,我们经常需要以不同的方式探测一个值,并在了解更多信息后执行不同的操作。TypeScript 理解这些检查并将其称为类型保护。类型检查器不再需要在每次使用变量时都向 TypeScript 确认其类型,而是利用一种称为控制流分析的技术来检查我们是否在给定代码段之前使用了类型保护。
¥In JavaScript, we often have to probe a value in different ways, and do something different once we know more about its type. TypeScript understands these checks and calls them type guards. Instead of having to convince TypeScript of a variable’s type whenever we use it, the type-checker leverages something called control flow analysis to see if we’ve used a type guard before a given piece of code.
例如,我们可以这样写:
¥For example, we can write something like
tsTry
functionfoo (arg : unknown) {if (typeofarg === "string") {console .log (arg .toUpperCase ());}}
在此示例中,我们检查了 arg
是否是 string
。TypeScript 识别出了 typeof arg === "string"
检查(它将其视为类型保护),并且知道 arg
是 if
块主体内的 string
。这让我们可以像访问 toUpperCase()
一样访问 string
的方法而不会出错。
¥In this example, we checked whether arg
was a string
.
TypeScript recognized the typeof arg === "string"
check, which it considered a type guard, and knew that arg
was a string
inside the body of the if
block.
That let us access string
methods like toUpperCase()
without getting an error.
但是,如果我们将条件移到名为 argIsString
的常量中,会发生什么?
¥However, what would happen if we moved the condition out to a constant called argIsString
?
ts
// In TS 4.3 and belowfunction foo(arg: unknown) {const argIsString = typeof arg === "string";if (argIsString) {console.log(arg.toUpperCase());// ~~~~~~~~~~~// Error! Property 'toUpperCase' does not exist on type 'unknown'.}}
在早期版本的 TypeScript 中,这将是一个错误。 - 即使 argIsString
被赋予了类型保护的值,TypeScript 也会丢失该信息。这很遗憾,因为我们可能想在多个地方重复使用相同的检查。为了解决这个问题,用户通常需要重复操作或使用类型断言(又称强制类型转换)。
¥In previous versions of TypeScript, this would be an error - even though argIsString
was assigned the value of a type guard, TypeScript simply lost that information.
That’s unfortunate since we might want to re-use the same check in several places.
To get around that, users often have to repeat themselves or use type assertions (a.k.a. casts).
在 TypeScript 4.4 中,情况不再如此。上面的例子运行正常,没有任何错误!当 TypeScript 发现我们正在测试一个常量值时,它会做一些额外的工作来检查它是否包含类型保护。如果该类型保护作用于 const
、readonly
属性或未修改的参数,则 TypeScript 能够适当地缩小该值的范围。
¥In TypeScript 4.4, that is no longer the case.
The above example works with no errors!
When TypeScript sees that we are testing a constant value, it will do a little bit of extra work to see if it contains a type guard.
If that type guard operates on a const
, a readonly
property, or an un-modified parameter, then TypeScript is able to narrow that value appropriately.
不同类型的类型保护条件会被保留 - 不仅仅是 typeof
检查。例如,对可区分联合的检查非常有效。
¥Different sorts of type guard conditions are preserved - not just typeof
checks.
For example, checks on discriminated unions work like a charm.
tsTry
typeShape =| {kind : "circle";radius : number }| {kind : "square";sideLength : number };functionarea (shape :Shape ): number {constisCircle =shape .kind === "circle";if (isCircle ) {// We know we have a circle here!returnMath .PI *shape .radius ** 2;} else {// We know we're left with a square here!returnshape .sideLength ** 2;}}
4.4 中对判别式的分析也更加深入。 - 我们现在可以提取判别式,TypeScript 可以缩小原始对象的范围。
¥Analysis on discriminants in 4.4 also goes a little bit deeper - we can now extract out discriminants and TypeScript can narrow the original object.
tsTry
typeShape =| {kind : "circle";radius : number }| {kind : "square";sideLength : number };functionarea (shape :Shape ): number {// Extract out the 'kind' field first.const {kind } =shape ;if (kind === "circle") {// We know we have a circle here!returnMath .PI *shape .radius ** 2;} else {// We know we're left with a square here!returnshape .sideLength ** 2;}}
再例如,这是一个检查其两个输入是否有内容的函数。
¥As another example, here’s a function that checks whether two of its inputs have contents.
tsTry
functiondoSomeChecks (inputA : string | undefined,inputB : string | undefined,shouldDoExtraWork : boolean) {constmustDoWork =inputA &&inputB &&shouldDoExtraWork ;if (mustDoWork ) {// We can access 'string' properties on both 'inputA' and 'inputB'!constupperA =inputA .toUpperCase ();constupperB =inputB .toUpperCase ();// ...}}
如果 mustDoWork
等于 true
,TypeScript 可以理解 inputA
和 inputB
都存在。这意味着我们不必像 inputA!
那样编写非空断言来让 TypeScript 相信 inputA
不是 undefined
。
¥TypeScript can understand that both inputA
and inputB
are both present if mustDoWork
is true
.
That means we don’t have to write a non-null assertion like inputA!
to convince TypeScript that inputA
isn’t undefined
.
这里的一个巧妙特性是,这种分析是可传递的。TypeScript 会跳过常量来理解你已经执行了哪些类型的检查。
¥One neat feature here is that this analysis works transitively. TypeScript will hop through constants to understand what sorts of checks you’ve already performed.
tsTry
functionf (x : string | number | boolean) {constisString = typeofx === "string";constisNumber = typeofx === "number";constisStringOrNumber =isString ||isNumber ;if (isStringOrNumber ) {x ;} else {x ;}}
请注意,存在一个截止点。 - TypeScript 在检查这些条件时不会任意深入,但其分析对于大多数检查来说已经足够深入。
¥Note that there’s a cutoff - TypeScript doesn’t go arbitrarily deep when checking these conditions, but its analysis is deep enough for most checks.
此功能应该可以让许多直观的 JavaScript 代码在 TypeScript 中 “正常工作”,而不会妨碍你。详情请见 查看 GitHub 上的实现!
¥This feature should make a lot of intuitive JavaScript code “just work” in TypeScript without it getting in your way. For more details, check out the implementation on GitHub!
符号和模板字符串模式索引签名
¥Symbol and Template String Pattern Index Signatures
TypeScript 允许我们使用索引签名来描述对象,其中每个属性都必须具有特定类型。这使我们能够将这些对象用作类似字典的类型,并使用方括号中的字符串键对其进行索引。
¥TypeScript lets us describe objects where every property has to have a certain type using index signatures. This allows us to use these objects as dictionary-like types, where we can use string keys to index into them with square brackets.
例如,我们可以编写一个带有索引签名的类型,该类型接受 string
键并映射到 boolean
值。如果我们尝试赋值除 boolean
值以外的任何值,都会出错。
¥For example, we can write a type with an index signature that takes string
keys and maps to boolean
values.
If we try to assign anything other than a boolean
value, we’ll get an error.
tsTry
interfaceBooleanDictionary {[key : string]: boolean;}declare letmyDict :BooleanDictionary ;// Valid to assign boolean valuesmyDict ["foo"] = true;myDict ["bar"] = false;// Error, "oops" isn't a booleanType 'string' is not assignable to type 'boolean'.2322Type 'string' is not assignable to type 'boolean'.myDict ["baz"] = "oops";
虽然 此处 Map
可能是一个更好的数据结构(特别是 Map<string, boolean>
),JavaScript 对象通常更易于使用,或者恰好是我们可以使用的对象。
¥While a Map
might be a better data structure here (specifically, a Map<string, boolean>
), JavaScript objects are often more convenient to use or just happen to be what we’re given to work with.
同样,Array<T>
已经定义了一个 number
索引签名,允许我们插入/检索 T
类型的值。
¥Similarly, Array<T>
already defines a number
index signature that lets us insert/retrieve values of type T
.
ts
// @errors: 2322 2375// This is part of TypeScript's definition of the built-in Array type.interface Array<T> {[index: number]: T;// ...}let arr = new Array<string>();// Validarr[0] = "hello!";// Error, expecting a 'string' value herearr[1] = 123;
索引签名对于在实际环境中表达大量代码非常有用;然而,到目前为止,它们仅限于 string
和 number
键(而 string
索引签名有一个故意设计的怪癖,即它们可以接受 number
键,因为它们无论如何都会被强制转换为字符串)。这意味着 TypeScript 不允许使用 symbol
键索引对象。TypeScript 也无法对某些 string
键子集的索引签名进行建模。 - 例如,索引签名仅描述名称以文本 data-
开头的属性。
¥Index signatures are very useful to express lots of code out in the wild;
however, until now they’ve been limited to string
and number
keys (and string
index signatures have an intentional quirk where they can accept number
keys since they’ll be coerced to strings anyway).
That means that TypeScript didn’t allow indexing objects with symbol
keys.
TypeScript also couldn’t model an index signature of some subset of string
keys - for example, an index signature which describes just properties whose names start with the text data-
.
TypeScript 4.4 解决了这些限制,并允许使用 symbol
和模板字符串模式的索引签名。
¥TypeScript 4.4 addresses these limitations, and allows index signatures for symbol
s and template string patterns.
例如,TypeScript 现在允许我们声明一个可以以任意 symbol
为键的类型。
¥For example, TypeScript now allows us to declare a type that can be keyed on arbitrary symbol
s.
tsTry
interfaceColors {[sym : symbol]: number;}constred =Symbol ("red");constgreen =Symbol ("green");constblue =Symbol ("blue");letcolors :Colors = {};// Assignment of a number is allowedcolors [red ] = 255;letredVal =colors [red ];Type 'string' is not assignable to type 'number'.2322Type 'string' is not assignable to type 'number'.colors [blue ] = "da ba dee";
同样,我们可以用模板字符串模式类型编写索引签名。它的用途之一是将以 data-
开头的属性从 TypeScript 的额外属性检查中豁免。当我们将对象字面量传递给具有预期类型的对象时,TypeScript 会查找未在预期类型中声明的多余属性。
¥Similarly, we can write an index signature with template string pattern type.
One use of this might be to exempt properties starting with data-
from TypeScript’s excess property checking.
When we pass an object literal to something with an expected type, TypeScript will look for excess properties that weren’t declared in the expected type.
ts
// @errors: 2322 2375interface Options {width?: number;height?: number;}let a: Options = {width: 100,height: 100,"data-blah": true,};interface OptionsWithDataProps extends Options {// Permit any property starting with 'data-'.[optName: `data-${string}`]: unknown;}let b: OptionsWithDataProps = {width: 100,height: 100,"data-blah": true,// Fails for a property which is not known, nor// starts with 'data-'"unknown-property": true,};
关于索引签名的最后一点是,它们现在允许使用联合类型,只要它们是无限域原始类型的联合。 - 具体来说:
¥A final note on index signatures is that they now permit union types, as long as they’re a union of infinite-domain primitive types - specifically:
-
string
-
number
-
symbol
-
模板字符串模式(例如
hello-${string}
)¥template string patterns (e.g.
`hello-${string}`
)
如果索引签名的参数是这些类型的并集,则该索引签名将被解糖为几个不同的索引签名。
¥An index signature whose argument is a union of these types will de-sugar into several different index signatures.
ts
interface Data {[optName: string | symbol]: any;}// Equivalent tointerface Data {[optName: string]: any;[optName: symbol]: any;}
详情请见 阅读拉取请求信息
¥For more details, read up on the pull request
在 Catch 变量中默认为 unknown
类型 (--useUnknownInCatchVariables
)
¥Defaulting to the unknown
Type in Catch Variables (--useUnknownInCatchVariables
)
在 JavaScript 中,任何类型的值都可以用 throw
抛出,并被 catch
子句捕获。因此,TypeScript 历来将 catch 子句变量的类型指定为 any
,并且不允许任何其他类型注解:
¥In JavaScript, any type of value can be thrown with throw
and caught in a catch
clause.
Because of this, TypeScript historically typed catch clause variables as any
, and would not allow any other type annotation:
ts
try {// Who knows what this might throw...executeSomeThirdPartyCode();} catch (err) {// err: anyconsole.error(err.message); // Allowed, because 'any'err.thisWillProbablyFail(); // Allowed, because 'any' :(}
TypeScript 添加 unknown
类型后,很明显,对于追求最高正确性和类型安全性的用户来说,在 catch
子句变量中使用 unknown
比 any
更好,因为它可以更好地缩小范围并强制我们针对任意值进行测试。最终,TypeScript 4.0 允许用户在每个 catch
子句变量上指定显式类型注释 unknown
(或 any
),以便我们可以根据具体情况选择更严格的类型;但是,对于某些人来说,在每个 catch
子句中手动指定 : unknown
是一件苦差事。
¥Once TypeScript added the unknown
type, it became clear that unknown
was a better choice than any
in catch
clause variables for users who want the highest degree of correctness and type-safety, since it narrows better and forces us to test against arbitrary values.
Eventually TypeScript 4.0 allowed users to specify an explicit type annotation of unknown
(or any
) on each catch
clause variable so that we could opt into stricter types on a case-by-case basis;
however, for some, manually specifying : unknown
on every catch
clause was a chore.
这就是为什么 TypeScript 4.4 引入了一个名为 useUnknownInCatchVariables
的新标志。此标志将 catch
子句变量的默认类型从 any
更改为 unknown
。
¥That’s why TypeScript 4.4 introduces a new flag called useUnknownInCatchVariables
.
This flag changes the default type of catch
clause variables from any
to unknown
.
tsTry
try {executeSomeThirdPartyCode ();} catch (err ) {// err: unknown// Error! Property 'message' does not exist on type 'unknown'.'err' is of type 'unknown'.18046'err' is of type 'unknown'.console .error (. err message );// Works! We can narrow 'err' from 'unknown' to 'Error'.if (err instanceofError ) {console .error (err .message );}}
此标志在 strict
系列选项下启用。这意味着如果你使用 strict
检查代码,此选项将自动启用。你可能会在 TypeScript 4.4 中遇到错误,例如
¥This flag is enabled under the strict
family of options.
That means that if you check your code using strict
, this option will automatically be turned on.
You may end up with errors in TypeScript 4.4 such as
Property 'message' does not exist on type 'unknown'.Property 'name' does not exist on type 'unknown'.Property 'stack' does not exist on type 'unknown'.
如果我们不想在 catch
子句中处理 unknown
变量,我们可以添加显式 : any
注释,这样就可以选择不使用更严格的类型。
¥In cases where we don’t want to deal with an unknown
variable in a catch
clause, we can always add an explicit : any
annotation so that we can opt out of stricter types.
tsTry
try {executeSomeThirdPartyCode ();} catch (err : any) {console .error (err .message ); // Works again!}
更多信息,请查看 实现拉取请求。
¥For more information, take a look at the implementing pull request.
精确可选属性类型 (--exactOptionalPropertyTypes
)
¥Exact Optional Property Types (--exactOptionalPropertyTypes
)
在 JavaScript 中,读取对象上缺失的属性会生成值 undefined
。也可以拥有值为 undefined
的实际属性。JavaScript 中的许多代码倾向于以相同的方式处理这些情况,因此最初 TypeScript 只是将每个可选属性解释为用户在类型中写入了 undefined
。例如,
¥In JavaScript, reading a missing property on an object produces the value undefined
.
It’s also possible to have an actual property with the value undefined
.
A lot of code in JavaScript tends to treat these situations the same way, and so initially TypeScript just interpreted every optional property as if a user had written undefined
in the type.
For example,
ts
interface Person {name: string;age?: number;}
被认为是等效的至
¥was considered equivalent to
ts
interface Person {name: string;age?: number | undefined;}
这意味着用户可以明确地用 undefined
代替 age
。
¥What this meant is that a user could explicitly write undefined
in place of age
.
ts
const p: Person = {name: "Daniel",age: undefined, // This is okay by default.};
默认情况下,TypeScript 不会区分值为 undefined
的现有属性和缺失属性。虽然这在大多数情况下都有效,但并非所有 JavaScript 代码都做出相同的假设。函数和运算符(例如 Object.assign
、Object.keys
、对象展开 ({ ...obj }
) 和 for
-in
循环)的行为会根据对象上是否存在属性而有所不同。在我们的 Person
示例中,如果在 age
属性的存在至关重要的上下文中观察到它,则可能会导致运行时错误。
¥So by default, TypeScript doesn’t distinguish between a present property with the value undefined
and a missing property.
While this works most of the time, not all code in JavaScript makes the same assumptions.
Functions and operators like Object.assign
, Object.keys
, object spread ({ ...obj }
), and for
-in
loops behave differently depending on whether or not a property actually exists on an object.
In the case of our Person
example, this could potentially lead to runtime errors if the age
property was observed in a context where its presence was important.
在 TypeScript 4.4 中,新的标志 exactOptionalPropertyTypes
指定可选属性类型应按原样解释,这意味着 | undefined
不会被添加到类型中:
¥In TypeScript 4.4, the new flag exactOptionalPropertyTypes
specifies that optional property types should be interpreted exactly as written, meaning that | undefined
is not added to the type:
tsTry
// With 'exactOptionalPropertyTypes' on:constType '{ name: string; age: undefined; }' is not assignable to type 'Person' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. Types of property 'age' are incompatible. Type 'undefined' is not assignable to type 'number'.2375Type '{ name: string; age: undefined; }' is not assignable to type 'Person' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. Types of property 'age' are incompatible. Type 'undefined' is not assignable to type 'number'.: p Person = {name : "Daniel",age :undefined , // Error! undefined isn't a number};
此标志不属于 strict
系列,如果你想要此行为,则需要明确启用。它还需要启用 strictNullChecks
。我们一直在更新 DefinitelyTyped 和其他定义,试图使转换尽可能简单,但你可能会遇到一些问题,具体取决于你的代码结构。
¥This flag is not part of the strict
family and needs to be turned on explicitly if you’d like this behavior.
It also requires strictNullChecks
to be enabled as well.
We’ve been making updates to DefinitelyTyped and other definitions to try to make the transition as straightforward as possible, but you may encounter some friction with this depending on how your code is structured.
更多信息,你可以查看 查看实现拉取请求此处。
¥For more information, you can take a look at the implementing pull request here.
static
类中的块
¥static
Blocks in Classes
TypeScript 4.4 支持 static
类中的块,这是一项即将推出的 ECMAScript 特性,可以帮助你编写更复杂的静态成员初始化代码。
¥TypeScript 4.4 brings support for static
blocks in classes, an upcoming ECMAScript feature that can help you write more-complex initialization code for static members.
tsTry
classFoo {staticcount = 0;// This is a static block:static {if (someCondition ()) {Foo .count ++;}}}
这些静态块允许你编写一系列具有独立作用域的语句,这些语句可以访问包含类中的私有字段。这意味着我们可以编写初始化代码,并且具有编写语句的所有功能,不会泄漏变量,并且可以完全访问类的内部结构。
¥These static blocks allow you to write a sequence of statements with their own scope that can access private fields within the containing class. That means that we can write initialization code with all the capabilities of writing statements, no leakage of variables, and full access to our class’s internals.
tsTry
classFoo {static #count = 0;getcount () {returnFoo .#count;}static {try {constlastInstances =loadLastInstances ();Foo .#count +=lastInstances .length ;}catch {}}}
如果没有 static
块,上面的代码也是可以编写的,但这通常会涉及几种不同类型的 hack,这些 hack 必须以某种方式妥协。
¥Without static
blocks, writing the code above was possible, but often involved several different types of hacks that had to compromise in some way.
请注意,一个类可以有多个 static
块,并且它们都是 ‘以与它们相同的顺序运行’ 编写的。
¥Note that a class can have multiple static
blocks, and they’re run in the same order in which they’re written.
tsTry
// Prints:// 1// 2// 3classFoo {staticprop = 1static {console .log (Foo .prop ++);}static {console .log (Foo .prop ++);}static {console .log (Foo .prop ++);}}
我们要感谢 Wenlu Wang 使用 TypeScript 实现了此功能。更多详情,请参阅 在此处查看该拉取请求。
¥We’d like to extend our thanks to Wenlu Wang for TypeScript’s implementation of this feature. For more details, you can see that pull request here.
tsc --help
更新和改进
¥tsc --help
Updates and Improvements
TypeScript 的 --help
选项已更新!得益于 Song Gao 的部分工作,我们对 更新编译器选项描述 和 重新设计 --help
菜单 进行了颜色和其他视觉区分方面的更改。
¥TypeScript’s --help
option has gotten a refresh!
Thanks to work in part by Song Gao, we’ve brought in changes to update the descriptions of our compiler options and restyle the --help
menu with colors and other visual separation.
你可以阅读有关 原始提案线程 的更多信息。
¥You can read more on the original proposal thread.
性能改进
¥Performance Improvements
更快的声明触发
¥Faster Declaration Emit
TypeScript 现在会缓存内部符号在不同上下文中是否可访问,以及如何打印特定类型。这些更改可以提升 TypeScript 在处理较为复杂类型的代码时的整体性能,尤其是在使用 declaration
标志生成 .d.ts
文件时。
¥TypeScript now caches whether internal symbols are accessible in different contexts, along with how specific types should be printed.
These changes can improve TypeScript’s general performance in code with fairly complex types, and is especially observed when emitting .d.ts
files under the declaration
flag.
更快的路径规范化
¥Faster Path Normalization
TypeScript 通常需要对文件路径执行几种类型的 “normalization” 操作,以使它们转换为编译器可以在任何地方使用的一致格式。这涉及到诸如用斜杠替换反斜杠,或删除路径中间的 /./
和 /../
段等操作。当 TypeScript 需要处理数百万个这样的路径时,这些操作最终会变得有点慢。在 TypeScript 4.4 中,路径首先会进行快速检查,以确定它们是否需要任何规范化。这些改进共同作用,将大型项目的加载时间缩短了 5-10%,在我们内部测试的大型项目中,效果更显著。
¥TypeScript often has to do several types of “normalization” on file paths to get them into a consistent format that the compiler can use everywhere.
This involves things like replacing backslashes with slashes, or removing intermediate /./
and /../
segments of paths.
When TypeScript has to operate over millions of these paths, these operations end up being a bit slow.
In TypeScript 4.4, paths first undergo quick checks to see whether they need any normalization in the first place.
These improvements together reduce project load time by 5-10% on bigger projects, and significantly more in massive projects that we’ve tested internally.
更多详情,请参阅 查看路径段规范化的 PR 和 斜线规范化的拉取请求。
¥For more details, you can view the PR for path segment normalization along with the PR for slash normalization.
更快的路径映射
¥Faster Path Mapping
TypeScript 现在会缓存其构建路径映射的方式(在 tsconfig.json
中使用 paths
选项)。对于包含数百个映射的项目,工作量的减少非常显著。你可以查看更多 关于此变更本身。
¥TypeScript now caches the way it constructs path-mappings (using the paths
option in tsconfig.json
).
For projects with several hundred mappings, the reduction is significant.
You can see more on the change itself.
使用 --strict
进行更快的增量构建
¥Faster Incremental Builds with --strict
实际上,如果启用了 strict
,TypeScript 最终会在 incremental
编译下重新进行类型检查工作。这实际上是一个 bug。这导致许多构建速度与关闭 incremental
一样慢。TypeScript 4.4 修复了这个问题,尽管这一变化也已反向移植到 TypeScript 4.3。
¥In what was effectively a bug, TypeScript would end up redoing type-checking work under incremental
compilations if strict
was on.
This led to many builds being just as slow as if incremental
was turned off.
TypeScript 4.4 fixes this, though the change has also been back-ported to TypeScript 4.3.
查看更多 此处。
¥See more here.
为大型输出更快地生成源码图
¥Faster Source Map Generation for Big Outputs
TypeScript 4.4 优化了在超大输出文件上生成源映射的功能。在构建旧版本的 TypeScript 编译器时,这可以将输出时间缩短约 8%。
¥TypeScript 4.4 adds an optimization for source map generation on extremely large output files. When building an older version of the TypeScript compiler, this results in around an 8% reduction in emit time.
我们要感谢 David Michon 提供了 简单干净的变更,从而实现了这一性能提升。
¥We’d like to extend our thanks to David Michon who provided a simple and clean change to enable this performance win.
更快的 --force
构建
¥Faster --force
Builds
在项目引用上使用 --build
模式时,TypeScript 必须执行最新检查以确定哪些文件需要重建。但是,在执行 --force
构建时,该信息无关紧要,因为每个项目依赖都将从头开始重建。在 TypeScript 4.4 中,--force
构建避免了这些不必要的步骤并启动完整构建。查看有关变更 此处 的更多信息。
¥When using --build
mode on project references, TypeScript has to perform up-to-date checks to determine which files need to be rebuilt.
When performing a --force
build, however, that information is irrelevant since every project dependency will be rebuilt from scratch.
In TypeScript 4.4, --force
builds avoid those unnecessary steps and start a full build.
See more about the change here.
JavaScript 拼写建议
¥Spelling Suggestions for JavaScript
TypeScript 为 Visual Studio 和 Visual Studio Code 等编辑器中的 JavaScript 编辑体验提供支持。大多数情况下,TypeScript 会尽量避免在 JavaScript 文件中出现问题;然而,TypeScript 通常拥有大量信息来提供可靠的建议,并且能够提供不太具侵入性的建议。
¥TypeScript powers the JavaScript editing experience in editors like Visual Studio and Visual Studio Code. Most of the time, TypeScript tries to stay out of the way in JavaScript files; however, TypeScript often has a lot of information to make confident suggestions, and ways of surfacing suggestions that aren’t too invasive.
这就是 TypeScript 现在会在纯 JavaScript 文件中提供拼写建议的原因。 - 没有 // @ts-check
或关闭 checkJs
的项目中。这些是 TypeScript 文件中已有的相同 “你的意思是……?” 建议,现在它们以某种形式存在于所有 JavaScript 文件中。
¥That’s why TypeScript now issues spelling suggestions in plain JavaScript files - ones without // @ts-check
or in a project with checkJs
turned off.
These are the same “Did you mean…?” suggestions that TypeScript files already have, and now they’re available in all JavaScript files in some form.
这些拼写建议可能会提供一些微妙的线索,表明你的代码是错误的。在测试此功能时,我们在现有代码中发现了一些错误!
¥These spelling suggestions can provide a subtle clue that your code is wrong. We managed to find a few bugs in existing code while testing this feature!
更多新功能详情,请访问 查看拉取请求!
¥For more details on this new feature, take a look at the pull request!
嵌入提示
¥Inlay Hints
TypeScript 4.4 支持嵌入提示,这有助于在代码中显示有用的信息,例如参数名称和返回类型。你可以将其视为一种友好的 “幽灵文本”。
¥TypeScript 4.4 provides support for inlay hints which can help display useful information like parameter names and return types in your code. You can think of it as a sort of friendly “ghost text”.
此功能由 Wenlu Wang 构建,他的 拉取请求 有更多详细信息。
¥This feature was built by Wenlu Wang whose pull request has more details.
Wenlu 还贡献了 在 Visual Studio Code 中集成嵌入提示,它已经作为 2021 年 7 月 (1.59) 版本的一部分 发布了。如果你想尝试嵌入提示,请确保你使用的是最新版本的 stable 或 insiders 编辑器。你还可以在 Visual Studio Code 的设置中修改嵌入提示的显示时间和位置。
¥Wenlu also contributed the integration for inlay hints in Visual Studio Code which has shipped as part of the July 2021 (1.59) release. If you’d like to try inlay hints out, make sure you’re using a recent stable or insiders version of the editor. You can also modify when and where inlay hints get displayed in Visual Studio Code’s settings.
自动导入在完成列表中显示真实路径
¥Auto-Imports Show True Paths in Completion Lists
当像 Visual Studio Code 这样的编辑器显示补全列表时,包含自动导入的补全会显示给定模块的路径;然而,此路径通常不是 TypeScript 最终放置在模块说明符中的路径。路径通常是相对于工作区的,这意味着如果你从像 moment
这样的包导入,你通常会看到像 node_modules/moment
这样的路径。
¥When editors like Visual Studio Code show a completion list, completions which include auto-imports are displayed with a path to the given module;
however, this path usually isn’t what TypeScript ends up placing in a module specifier.
The path is usually something relative to the workspace, meaning that if you’re importing from a package like moment
, you’ll often see a path like node_modules/moment
.
这些路径最终会变得笨拙且经常产生误导,尤其是考虑到实际插入到文件中的路径需要考虑 Node 的 node_modules
解析、路径映射、符号链接和重新导出。
¥These paths end up being unwieldy and often misleading, especially given that the path that actually gets inserted into your file needs to consider Node’s node_modules
resolution, path mappings, symlinks, and re-exports.
因此,在 TypeScript 4.4 中,补全项标签现在会显示用于导入的实际模块路径!
¥That’s why with TypeScript 4.4, the completion item label now shows the actual module path that will be used for the import!
由于此计算可能很昂贵,包含许多自动导入的补全列表可能会在你输入更多字符时批量填充最终的模块说明符。你可能仍然会看到旧的工作区相对路径标签;但是,当你编辑 “预热” 时,它们应该在再次按下一两次键后被替换为实际路径。
¥Since this calculation can be expensive, completion lists containing many auto-imports may fill in the final module specifiers in batches as you type more characters. It’s possible that you’ll still sometimes see the old workspace-relative path labels; however, as your editing experience “warms up”, they should get replaced with the actual path after another keystroke or two.
打破变更
¥Breaking Changes
lib.d.ts
TypeScript 4.4 的变更
¥lib.d.ts
Changes for TypeScript 4.4
与每个 TypeScript 版本一样,lib.d.ts
的声明(尤其是为 Web 上下文生成的声明)都已更改。你可以查阅 我们已知的 lib.dom.d.ts
变更列表 以了解受影响的内容。
¥As with every TypeScript version, declarations for lib.d.ts
(especially the declarations generated for web contexts), have changed.
You can consult our list of known lib.dom.d.ts
changes to understand what is impacted.
导入函数的间接调用更合规
¥More-Compliant Indirect Calls for Imported Functions
在早期版本的 TypeScript 中,从 CommonJS、AMD 和其他非 ES 模块系统调用导入会设置被调用函数的 this
值。具体来说,在以下示例中,调用 fooModule.foo()
时,foo()
方法会将 fooModule
设置为 this
的值。
¥In earlier versions of TypeScript, calling an import from CommonJS, AMD, and other non-ES module systems would set the this
value of the called function.
Specifically, in the following example, when calling fooModule.foo()
, the foo()
method will have fooModule
set as the value of this
.
ts
// Imagine this is our imported module, and it has an export named 'foo'.let fooModule = {foo() {console.log(this);},};fooModule.foo();
这并非 ECMAScript 中导出函数调用时应有的工作方式。这就是为什么 TypeScript 4.4 在调用导入函数时会故意丢弃 this
值,方法是使用以下方式。
¥This is not the way exported functions in ECMAScript are supposed to work when we call them.
That’s why TypeScript 4.4 intentionally discards the this
value when calling imported functions, by using the following emit.
ts
// Imagine this is our imported module, and it has an export named 'foo'.let fooModule = {foo() {console.log(this);},};// Notice we're actually calling '(0, fooModule.foo)' now, which is subtly different.(0, fooModule.foo)();
你可以 在此处阅读有关变更的更多信息。
¥You can read up more about the changes here.
在 Catch 变量中使用 unknown
¥Using unknown
in Catch Variables
使用 strict
标志运行的用户可能会看到有关 catch
变量为 unknown
的新错误,尤其是在现有代码假定仅捕获了 Error
值的情况下。这通常会导致错误消息,例如:
¥Users running with the strict
flag may see new errors around catch
variables being unknown
, especially if the existing code assumes only Error
values have been caught.
This often results in error messages such as:
Property 'message' does not exist on type 'unknown'.Property 'name' does not exist on type 'unknown'.Property 'stack' does not exist on type 'unknown'.
为了解决这个问题,你可以专门添加运行时检查,以确保抛出的类型与你预期的类型匹配。否则,你可以使用类型断言,在 catch 变量中添加显式 : any
,或者关闭 useUnknownInCatchVariables
。
¥To get around this, you can specifically add runtime checks to ensure that the thrown type matches your expected type.
Otherwise, you can just use a type assertion, add an explicit : any
to your catch variable, or turn off useUnknownInCatchVariables
.
更广泛的 Always-Truthy Promise 检查
¥Broader Always-Truthy Promise Checks
在早期版本中,TypeScript 引入了 “始终进行 Truthy Promise 检查” 来捕获可能忘记 await
的代码;但是,检查仅适用于命名声明。这意味着虽然这段代码会正确地收到错误……
¥In prior versions, TypeScript introduced “Always Truthy Promise checks” to catch code where an await
may have been forgotten;
however, the checks only applied to named declarations.
That meant that while this code would correctly receive an error…
ts
async function foo(): Promise<boolean> {return false;}async function bar(): Promise<string> {const fooResult = foo();if (fooResult) {// <- error! :Dreturn "true";}return "false";}
……以下代码不会。
¥…the following code would not.
ts
async function foo(): Promise<boolean> {return false;}async function bar(): Promise<string> {if (foo()) {// <- no error :(return "true";}return "false";}
TypeScript 4.4 现在同时标记了这两者。更多信息请见 阅读原始变更信息!
¥TypeScript 4.4 now flags both. For more information, read up on the original change.
抽象属性不允许初始化函数
¥Abstract Properties Do Not Allow Initializers
以下代码现在会出错,因为抽象属性可能没有初始化器:
¥The following code is now an error because abstract properties may not have initializers:
ts
abstract class C {abstract prop = 1;// ~~~~// Property 'prop' cannot have an initializer because it is marked abstract.}
相反,你只能为属性指定一个类型:
¥Instead, you may only specify a type for the property:
ts
abstract class C {abstract prop: number;}