TypeScript 4.4

别名条件和判别式的控制流分析

¥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

ts
function foo(arg: unknown) {
if (typeof arg === "string") {
console.log(arg.toUpperCase());
(parameter) arg: string
}
}
Try

在此示例中,我们检查了 arg 是否是 string。TypeScript 识别出了 typeof arg === "string" 检查(它将其视为类型保护),并且知道 argif 块主体内的 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 below
function 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 发现我们正在测试一个常量值时,它会做一些额外的工作来检查它是否包含类型保护。如果该类型保护作用于 constreadonly 属性或未修改的参数,则 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.

ts
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; sideLength: number };
 
function area(shape: Shape): number {
const isCircle = shape.kind === "circle";
if (isCircle) {
// We know we have a circle here!
return Math.PI * shape.radius ** 2;
} else {
// We know we're left with a square here!
return shape.sideLength ** 2;
}
}
Try

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.

ts
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; sideLength: number };
 
function area(shape: Shape): number {
// Extract out the 'kind' field first.
const { kind } = shape;
 
if (kind === "circle") {
// We know we have a circle here!
return Math.PI * shape.radius ** 2;
} else {
// We know we're left with a square here!
return shape.sideLength ** 2;
}
}
Try

再例如,这是一个检查其两个输入是否有内容的函数。

¥As another example, here’s a function that checks whether two of its inputs have contents.

ts
function doSomeChecks(
inputA: string | undefined,
inputB: string | undefined,
shouldDoExtraWork: boolean
) {
const mustDoWork = inputA && inputB && shouldDoExtraWork;
if (mustDoWork) {
// We can access 'string' properties on both 'inputA' and 'inputB'!
const upperA = inputA.toUpperCase();
const upperB = inputB.toUpperCase();
// ...
}
}
Try

如果 mustDoWork 等于 true,TypeScript 可以理解 inputAinputB 都存在。这意味着我们不必像 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.

ts
function f(x: string | number | boolean) {
const isString = typeof x === "string";
const isNumber = typeof x === "number";
const isStringOrNumber = isString || isNumber;
if (isStringOrNumber) {
x;
(parameter) x: string | number
} else {
x;
(parameter) x: boolean
}
}
Try

请注意,存在一个截止点。 - 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.

ts
interface BooleanDictionary {
[key: string]: boolean;
}
 
declare let myDict: BooleanDictionary;
 
// Valid to assign boolean values
myDict["foo"] = true;
myDict["bar"] = false;
 
// Error, "oops" isn't a boolean
myDict["baz"] = "oops";
Type 'string' is not assignable to type 'boolean'.2322Type 'string' is not assignable to type 'boolean'.
Try

虽然 此处 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>();
// Valid
arr[0] = "hello!";
// Error, expecting a 'string' value here
arr[1] = 123;

索引签名对于在实际环境中表达大量代码非常有用;然而,到目前为止,它们仅限于 stringnumber 键(而 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 symbols and template string patterns.

例如,TypeScript 现在允许我们声明一个可以以任意 symbol 为键的类型。

¥For example, TypeScript now allows us to declare a type that can be keyed on arbitrary symbols.

ts
interface Colors {
[sym: symbol]: number;
}
 
const red = Symbol("red");
const green = Symbol("green");
const blue = Symbol("blue");
 
let colors: Colors = {};
 
// Assignment of a number is allowed
colors[red] = 255;
let redVal = colors[red];
let redVal: number
 
colors[blue] = "da ba dee";
Type 'string' is not assignable to type 'number'.2322Type 'string' is not assignable to type 'number'.
Try

同样,我们可以用模板字符串模式类型编写索引签名。它的用途之一是将以 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 2375
interface 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 to
interface 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: any
console.error(err.message); // Allowed, because 'any'
err.thisWillProbablyFail(); // Allowed, because 'any' :(
}

TypeScript 添加 unknown 类型后,很明显,对于追求最高正确性和类型安全性的用户来说,在 catch 子句变量中使用 unknownany 更好,因为它可以更好地缩小范围并强制我们针对任意值进行测试。最终,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.

ts
try {
executeSomeThirdPartyCode();
} catch (err) {
// err: unknown
 
// Error! Property 'message' does not exist on type 'unknown'.
console.error(err.message);
'err' is of type 'unknown'.18046'err' is of type 'unknown'.
 
// Works! We can narrow 'err' from 'unknown' to 'Error'.
if (err instanceof Error) {
console.error(err.message);
}
}
Try

此标志在 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.

ts
try {
executeSomeThirdPartyCode();
} catch (err: any) {
console.error(err.message); // Works again!
}
Try

更多信息,请查看 实现拉取请求

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

ts
// With 'exactOptionalPropertyTypes' on:
const p: Person = {
Type '{ 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'.
name: "Daniel",
age: undefined, // Error! undefined isn't a number
};
Try

此标志不属于 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.

ts
class Foo {
static count = 0;
 
// This is a static block:
static {
if (someCondition()) {
Foo.count++;
}
}
}
Try

这些静态块允许你编写一系列具有独立作用域的语句,这些语句可以访问包含类中的私有字段。这意味着我们可以编写初始化代码,并且具有编写语句的所有功能,不会泄漏变量,并且可以完全访问类的内部结构。

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

ts
class Foo {
static #count = 0;
 
get count() {
return Foo.#count;
}
 
static {
try {
const lastInstances = loadLastInstances();
Foo.#count += lastInstances.length;
}
catch {}
}
}
Try

如果没有 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.

ts
// Prints:
// 1
// 2
// 3
class Foo {
static prop = 1
static {
console.log(Foo.prop++);
}
static {
console.log(Foo.prop++);
}
static {
console.log(Foo.prop++);
}
}
Try

我们要感谢 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.

The new TypeScript --help menu where the output is bucketed into several different areas

你可以阅读有关 原始提案线程 的更多信息。

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

查看此处更多详细信息

¥See more details here.

更快的路径规范化

¥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”.

A preview of inlay hints in Visual Studio Code

此功能由 Wenlu Wang 构建,他的 拉取请求 有更多详细信息。

¥This feature was built by Wenlu Wang whose pull request has more details.

Wenlu 还贡献了 在 Visual Studio Code 中集成嵌入提示,它已经作为 2021 年 7 月 (1.59) 版本的一部分 发布了。如果你想尝试嵌入提示,请确保你使用的是最新版本的 stableinsiders 编辑器。你还可以在 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.

A completion list containing unwieldy paths containing 'node_modules'. For example, the label for 'calendarFormat' is 'node_modules/moment/moment' instead of '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!

A completion list containing clean paths with no intermediate 'node_modules'. For example, the label for 'calendarFormat' is 'moment' instead of 'node_modules/moment/moment'.

由于此计算可能很昂贵,包含许多自动导入的补全列表可能会在你输入更多字符时批量填充最终的模块说明符。你可能仍然会看到旧的工作区相对路径标签;但是,当你编辑 “预热” 时,它们应该在再次按下一两次键后被替换为实际路径。

¥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! :D
return "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;
}