可变元组类型
🌐 Variadic Tuple Types
考虑一个名为 concat 的 JavaScript 函数,它接受两个数组或元组类型,并将它们连接在一起形成一个新的数组。
🌐 Consider a function in JavaScript called concat that takes two array or tuple types and concatenates them together to make a new array.
jsfunction concat(arr1, arr2) {return [...arr1, ...arr2];}
还要考虑 tail,它接收一个数组或元组,并返回除第一个之外的所有元素。
🌐 Also consider tail, that takes an array or tuple, and returns all elements but the first.
jsfunction tail(arg) {const [_, ...result] = arg;return result;}
如何在 TypeScript 中为这两个函数编写类型?
🌐 How would we type either of these in TypeScript?
对于 concat,在语言的旧版本中,我们能做的唯一有效方法就是尝试编写一些重载。
🌐 For concat, the only valid thing we could do in older versions of the language was to try and write some overloads.
tsfunction concat(arr1: [], arr2: []): [];function concat<A>(arr1: [A], arr2: []): [A];function concat<A, B>(arr1: [A, B], arr2: []): [A, B];function concat<A, B, C>(arr1: [A, B, C], arr2: []): [A, B, C];function concat<A, B, C, D>(arr1: [A, B, C, D], arr2: []): [A, B, C, D];function concat<A, B, C, D, E>(arr1: [A, B, C, D, E], arr2: []): [A, B, C, D, E];function concat<A, B, C, D, E, F>(arr1: [A, B, C, D, E, F], arr2: []): [A, B, C, D, E, F];
呃……好吧,那就是……当第二个数组总是空的时候的七个重载。我们来为 arr2 有一个参数的情况添加一些重载。
🌐 Uh…okay, that’s…seven overloads for when the second array is always empty.
Let’s add some for when arr2 has one argument.
tsfunction concat<A2>(arr1: [], arr2: [A2]): [A2];function concat<A1, A2>(arr1: [A1], arr2: [A2]): [A1, A2];function concat<A1, B1, A2>(arr1: [A1, B1], arr2: [A2]): [A1, B1, A2];function concat<A1, B1, C1, A2>(arr1: [A1, B1, C1], arr2: [A2]): [A1, B1, C1, A2];function concat<A1, B1, C1, D1, A2>(arr1: [A1, B1, C1, D1], arr2: [A2]): [A1, B1, C1, D1, A2];function concat<A1, B1, C1, D1, E1, A2>(arr1: [A1, B1, C1, D1, E1], arr2: [A2]): [A1, B1, C1, D1, E1, A2];function concat<A1, B1, C1, D1, E1, F1, A2>(arr1: [A1, B1, C1, D1, E1, F1], arr2: [A2]): [A1, B1, C1, D1, E1, F1, A2];
我们希望很明显,这已经变得不合理了。不幸的是,如果你像输入 tail 这样的函数,也会遇到同样类型的问题。
🌐 We hope it’s clear that this is getting unreasonable.
Unfortunately, you’d also end up with the same sorts of issues typing a function like tail.
这是我们所谓的“千刀万剐式重载”的又一个例子,而且它甚至不能普遍解决问题。 它只能为我们愿意编写的那些重载提供正确的类型。 如果我们想做一个万能情况,我们需要像下面这样的重载:
🌐 This is another case of what we like to call “death by a thousand overloads”, and it doesn’t even solve the problem generally. It only gives correct types for as many overloads as we care to write. If we wanted to make a catch-all case, we’d need an overload like the following:
tsfunction concat<T, U>(arr1: T[], arr2: U[]): Array<T | U>;
但是,当使用元组时,该签名不会编码任何有关输入长度或元素顺序的信息。
🌐 But that signature doesn’t encode anything about the lengths of the input, or the order of the elements, when using tuples.
TypeScript 4.0 带来了两项根本性的变化以及推断改进,使这些类型识别成为可能。
🌐 TypeScript 4.0 brings two fundamental changes, along with inference improvements, to make typing these possible.
第一个变化是元组类型语法中的展开现在可以是泛型的。这意味着即使我们不知道实际操作的类型,也可以表示对元组和数组的高阶操作。当在这些元组类型中实例化泛型展开(或者用真实类型替换它们)时,它们可以生成其他数组和元组类型的集合。
🌐 The first change is that spreads in tuple type syntax can now be generic. This means that we can represent higher-order operations on tuples and arrays even when we don’t know the actual types we’re operating over. When generic spreads are instantiated (or, replaced with a real type) in these tuple types, they can produce other sets of array and tuple types.
例如,这意味着我们可以输入像 tail 这样的函数,而不会遇到“无数重载带来的困扰”问题。
🌐 For example, that means we can type function like tail, without our “death by a thousand overloads” issue.
tsTryfunctiontail <T extends any[]>(arr : readonly [any, ...T ]) {const [_ignored , ...rest ] =arr ;returnrest ;}constmyTuple = [1, 2, 3, 4] asconst ;constmyArray = ["hello", "world"];constr1 =tail (myTuple );constr2 =tail ([...myTuple , ...myArray ] asconst );
第二个变化是剩余元素可以出现在元组中的任何位置,而不仅仅是在末尾!
🌐 The second change is that rest elements can occur anywhere in a tuple - not just at the end!
tstype Strings = [string, string];type Numbers = [number, number];type StrStrNumNumBool = [...Strings, ...Numbers, boolean];
以前,TypeScript 会触发如下错误:
🌐 Previously, TypeScript would issue an error like the following:
A rest element must be last in a tuple type.
但在 TypeScript 4.0 中,此限制放宽了。
🌐 But with TypeScript 4.0, this restriction is relaxed.
请注意,当我们扩展一个长度未知的类型时,生成的类型也会变得无界,并且所有后续元素都会被计入生成的剩余元素类型。
🌐 Note that in cases when we spread in a type without a known length, the resulting type becomes unbounded as well, and all the following elements factor into the resulting rest element type.
tstype Strings = [string, string];type Numbers = number[];type Unbounded = [...Strings, ...Numbers, boolean];
通过将这两种行为结合在一起,我们可以为 concat 写出一个单一的类型良好的签名:
🌐 By combining both of these behaviors together, we can write a single well-typed signature for concat:
tsTrytypeArr = readonly any[];functionconcat <T extendsArr ,U extendsArr >(arr1 :T ,arr2 :U ): [...T , ...U ] {return [...arr1 , ...arr2 ];}
虽然该签名仍然有点长,但它只是一个不需要重复的签名,并且它为所有数组和元组提供了可预测的行为。
🌐 While that one signature is still a bit lengthy, it’s just one signature that doesn’t have to be repeated, and it gives predictable behavior on all arrays and tuples.
这个功能本身就很棒,但在更复杂的场景中也同样出色。
例如,考虑一个用于部分应用参数的函数,叫做 partialCall。
partialCall 接受一个函数——我们称之为 f ——以及 f 期望的最初几个参数。
然后它返回一个新函数,该函数接受 f 仍然需要的其他参数,并在接收到这些参数时调用 f。
🌐 This functionality on its own is great, but it shines in more sophisticated scenarios too.
For example, consider a function to partially apply arguments called partialCall.
partialCall takes a function - let’s call it f - along with the initial few arguments that f expects.
It then returns a new function that takes any other arguments that f still needs, and calls f when it receives them.
jsfunction partialCall(f, ...headArgs) {return (...tailArgs) => f(...headArgs, ...tailArgs);}
TypeScript 4.0 改进了对剩余参数和剩余元组元素的推断过程,这样我们可以对其进行类型定义并让它“直接工作”。
🌐 TypeScript 4.0 improves the inference process for rest parameters and rest tuple elements so that we can type this and have it “just work”.
tsTrytypeArr = readonly unknown[];functionpartialCall <T extendsArr ,U extendsArr ,R >(f : (...args : [...T , ...U ]) =>R ,...headArgs :T ) {return (...tailArgs :U ) =>f (...headArgs , ...tailArgs );}
在这种情况下,partialCall 明白它最初可以和不可以接受哪些参数,并返回相应地接受和拒绝剩余参数的函数。
🌐 In this case, partialCall understands which parameters it can and can’t initially take, and returns functions that appropriately accept and reject anything left over.
tsTryconstfoo = (x : string,y : number,z : boolean) => {};constArgument of type 'number' is not assignable to parameter of type 'string'.2345Argument of type 'number' is not assignable to parameter of type 'string'.f1 =partialCall (foo ,100 );constExpected 4 arguments, but got 5.2554Expected 4 arguments, but got 5.f2 =partialCall (foo , "hello", 100, true,"oops" );// This works!constf3 =partialCall (foo , "hello");// What can we do with f3 now?// Works!f3 (123, true);Expected 2 arguments, but got 0.2554Expected 2 arguments, but got 0.(); f3 Argument of type 'string' is not assignable to parameter of type 'boolean'.2345Argument of type 'string' is not assignable to parameter of type 'boolean'.f3 (123,"hello" );
可变元组类型带来了许多新的令人兴奋的模式,尤其是在函数组合方面。我们希望能够利用它更好地对 JavaScript 内置的 bind 方法进行类型检查。此外,还有一些其他推断改进和模式也包含在内,如果你有兴趣了解更多,可以查看关于可变元组的拉取请求。
🌐 Variadic tuple types enable a lot of new exciting patterns, especially around function composition.
We expect we may be able to leverage it to do a better job type-checking JavaScript’s built-in bind method.
A handful of other inference improvements and patterns also went into this, and if you’re interested in learning more, you can take a look at the pull request for variadic tuples.
带标签的元组元素
🌐 Labeled Tuple Elements
改进元组类型和参数列表的使用体验非常重要,因为它可以让我们对常见的 JavaScript 习惯用法进行强类型验证——实际上就是对参数列表进行切分和操作,并将其传递给其他函数。可以在剩余参数中使用元组类型的想法是这一点非常关键的一个例子。
🌐 Improving the experience around tuple types and parameter lists is important because it allows us to get strongly typed validation around common JavaScript idioms - really just slicing and dicing argument lists and passing them to other functions. The idea that we can use tuple types for rest parameters is one place where this is crucial.
例如,以下函数使用元组类型作为剩余参数……
🌐 For example, the following function that uses a tuple type as a rest parameter…
tsfunction foo(...args: [string, number]): void {// ...}
……应该与以下函数看起来没有什么不同……
tsfunction foo(arg0: string, arg1: number): void {// ...}
…对于 foo 的任何调用者。
tsTryfoo ("hello", 42);Expected 2 arguments, but got 3.2554Expected 2 arguments, but got 3.foo ("hello", 42,true );Expected 2 arguments, but got 1.2554Expected 2 arguments, but got 1.("hello"); foo
不过,有一个地方开始出现差异,那就是可读性。 在第一个例子中,前两个元素没有参数名称。 虽然这些对类型检查没有影响,但元组位置缺少标签可能会使其更难使用——更难传达我们的意图。
🌐 There is one place where the differences begin to become observable though: readability. In the first example, we have no parameter names for the first and second elements. While these have no impact on type-checking, the lack of labels on tuple positions can make them harder to use - harder to communicate our intent.
这就是 TypeScript 4.0 中元组类型现在可以提供标签的原因。
🌐 That’s why in TypeScript 4.0, tuples types can now provide labels.
tstype Range = [start: number, end: number];
为了加深参数列表和元组类型之间的联系,剩余元素和可选元素的语法与参数列表的语法相同。
🌐 To deepen the connection between parameter lists and tuple types, the syntax for rest elements and optional elements mirrors the syntax for parameter lists.
tstype Foo = [first: number, second?: string, ...rest: any[]];
在使用带标签的元组时有一些规则。首先,当为元组中的某个元素添加标签时,元组中的其他所有元素也必须添加标签。
🌐 There are a few rules when using labeled tuples. For one, when labeling a tuple element, all other elements in the tuple must also be labeled.
tsTrytypeBar = [first : string, number];
值得注意的是——在解构时,标签并不要求我们给变量起不同的名字。它们纯粹是用于文档记录和工具的。
🌐 It’s worth noting - labels don’t require us to name our variables differently when destructuring. They’re purely there for documentation and tooling.
tsTryfunctionfoo (x : [first : string,second : number]) {// ...// note: we didn't need to name these 'first' and 'second'const [a ,b ] =x ;a b }
总的来说,当利用元组和参数列表的模式以及以类型安全的方式实现重载时,带标签的元组非常方便。实际上,TypeScript 的编辑器支持会在可能的情况下尝试将它们显示为重载。
🌐 Overall, labeled tuples are handy when taking advantage of patterns around tuples and argument lists, along with implementing overloads in a type-safe way. In fact, TypeScript’s editor support will try to display them as overloads when possible.

要了解更多信息,请查看关于带标签元组元素的拉取请求。
🌐 To learn more, check out the pull request for labeled tuple elements.
从构造函数推断类属性
🌐 Class Property Inference from Constructors
TypeScript 4.0 现在可以在启用 noImplicitAny 时使用控制流分析来确定类中属性的类型。
🌐 TypeScript 4.0 can now use control flow analysis to determine the types of properties in classes when noImplicitAny is enabled.
tsTryclassSquare {// Previously both of these were anyarea ;sideLength ;constructor(sideLength : number) {this.sideLength =sideLength ;this.area =sideLength ** 2;}}
在构造函数的所有路径并非都对实例成员进行赋值的情况下,该属性被认为可能是 undefined。
🌐 In cases where not all paths of a constructor assign to an instance member, the property is considered to potentially be undefined.
tsTryclassSquare {sideLength ;constructor(sideLength : number) {if (Math .random ()) {this.sideLength =sideLength ;}}getarea () {return this.Object is possibly 'undefined'.2532Object is possibly 'undefined'.sideLength ** 2;}}
在你更清楚情况的情况下(例如,你有某种 initialize 方法),如果你在 strictPropertyInitialization 中,你仍然需要显式的类型注解以及明确的赋值断言(!)。
🌐 In cases where you know better (e.g. you have an initialize method of some sort), you’ll still need an explicit type annotation along with a definite assignment assertion (!) if you’re in strictPropertyInitialization.
tsTryclassSquare {// definite assignment assertion// vsideLength !: number;// type annotationconstructor(sideLength : number) {this.initialize (sideLength );}initialize (sideLength : number) {this.sideLength =sideLength ;}getarea () {return this.sideLength ** 2;}}
欲了解更多详情,请参阅实现该功能的拉取请求。
🌐 For more details, see the implementing pull request.
短路赋值运算符
🌐 Short-Circuiting Assignment Operators
JavaScript 以及许多其他语言支持一组称为 复合赋值 的运算符。复合赋值运算符将一个运算符应用于两个操作数,然后将结果赋值给左侧。你可能以前见过这些运算符:
🌐 JavaScript, and a lot of other languages, support a set of operators called compound assignment operators. Compound assignment operators apply an operator to two arguments, and then assign the result to the left side. You may have seen these before:
ts// Addition// a = a + ba += b;// Subtraction// a = a - ba -= b;// Multiplication// a = a * ba *= b;// Division// a = a / ba /= b;// Exponentiation// a = a ** ba **= b;// Left Bit Shift// a = a << ba <<= b;
JavaScript 中有许多运算符都有对应的赋值运算符!然而,直到最近,有三个显著的例外:逻辑与(&&)、逻辑或(||)以及空值合并运算符(??)。
🌐 So many operators in JavaScript have a corresponding assignment operator!
Up until recently, however, there were three notable exceptions: logical and (&&), logical or (||), and nullish coalescing (??).
这就是为什么 TypeScript 4.0 支持一个新的 ECMAScript 特性,可以添加三种新的赋值运算符:&&=、||= 和 ??=。
🌐 That’s why TypeScript 4.0 supports a new ECMAScript feature to add three new assignment operators: &&=, ||=, and ??=.
这些操作符非常适合替换用户可能编写如下代码的任何示例:
🌐 These operators are great for substituting any example where a user might write code like the following:
tsa = a && b;a = a || b;a = a ?? b;
或者类似的 if 块,如
🌐 Or a similar if block like
ts// could be 'a ||= b'if (!a) {a = b;}
我们甚至见过(或者,呃,我们自己编写的)一些模式,仅在需要时才延迟初始化值。
🌐 There are even some patterns we’ve seen (or, uh, written ourselves) to lazily initialize values, only if they’ll be needed.
tslet values: string[];(values ?? (values = [])).push("hello");// After(values ??= []).push("hello");
(看,我们并不为我们写的所有代码感到自豪…)
在极少数情况下,如果你使用带有副作用的 getter 或 setter,值得注意的是,这些操作符只有在必要时才执行赋值。从这个意义上说,不仅操作符右侧会被“短路”,赋值本身也是如此。
🌐 On the rare case that you use getters or setters with side-effects, it’s worth noting that these operators only perform assignments if necessary. In that sense, not only is the right side of the operator “short-circuited” - the assignment itself is too.
tsobj.prop ||= foo();// roughly equivalent to either of the followingobj.prop || (obj.prop = foo());if (!obj.prop) {obj.prop = foo();}
试着运行以下示例,看看这与_总是_执行赋值有何不同。
tsTryconstobj = {getprop () {console .log ("getter has run");// Replace me!returnMath .random () < 0.5;},setprop (_val : boolean) {console .log ("setter has run");}};functionfoo () {console .log ("right side evaluated");return true;}console .log ("This one always runs the setter");obj .prop =obj .prop ||foo ();console .log ("This one *sometimes* runs the setter");obj .prop ||=foo ();
我们要特别感谢社区成员 Wenlu Wang 的这次贡献!
🌐 We’d like to extend a big thanks to community member Wenlu Wang for this contribution!
欲了解更多详情,你可以在此查看拉取请求。 你也可以查看 TC39 关于此功能的提案仓库。
🌐 For more details, you can take a look at the pull request here. You can also check out TC39’s proposal repository for this feature.
catch 上的 unknown 条款绑定
🌐 unknown on catch Clause Bindings
自 TypeScript 诞生之初,catch 子句变量就一直被类型化为 any。这意味着 TypeScript 允许你对它们做任何想做的事情。
🌐 Since the beginning days of TypeScript, catch clause variables have always been typed as any.
This meant that TypeScript allowed you to do anything you wanted with them.
tsTrytry {// Do some work} catch (x ) {// x has type 'any' - have fun!console .log (x .message );console .log (x .toUpperCase ());x ++;x .yadda .yadda .yadda ();}
如果我们试图防止我们的错误处理代码中发生更多的错误,上述做法会有一些不良行为!由于这些变量默认类型为 any,它们缺乏任何类型安全性,这可能会在执行无效操作时产生错误。
🌐 The above has some undesirable behavior if we’re trying to prevent more errors from happening in our error-handling code!
Because these variables have the type any by default, they lack any type-safety which could have errored on invalid operations.
这就是为什么 TypeScript 4.0 现在允许你将 catch 子句变量的类型指定为 unknown。unknown 比 any 更安全,因为它提醒我们在操作值之前需要执行某些类型检查。
🌐 That’s why TypeScript 4.0 now lets you specify the type of catch clause variables as unknown instead.
unknown is safer than any because it reminds us that we need to perform some sorts of type-checks before operating on our values.
tsTrytry {// ...} catch (e : unknown) {// Can't access values on unknowns'e' is of type 'unknown'.18046'e' is of type 'unknown'.console .log (. e toUpperCase ());if (typeofe === "string") {// We've narrowed 'e' down to the type 'string'.console .log (e .toUpperCase ());}}
虽然默认情况下 catch 变量的类型不会改变,但我们未来可能会考虑一个新的 strict 模式标志,以便用户可以选择启用这种行为。与此同时,应该可以编写一个 lint 规则来强制 catch 变量必须明确注解为 : any 或 : unknown。
🌐 While the types of catch variables won’t change by default, we might consider a new strict mode flag in the future so that users can opt in to this behavior.
In the meantime, it should be possible to write a lint rule to force catch variables to have an explicit annotation of either : any or : unknown.
欲了解更多详情,你可以查看此功能的更改。
🌐 For more details you can peek at the changes for this feature.
自定义 JSX 工厂
🌐 Custom JSX Factories
在使用 JSX 时,fragment 是一种 JSX 元素类型,允许我们返回多个子元素。 当我们最初在 TypeScript 中实现 fragments 时,对于其他库将如何使用它们,我们并没有很好的概念。 如今,大多数鼓励使用 JSX 并支持 fragments 的其他库,其 API 形态都非常相似。
🌐 When using JSX, a fragment is a type of JSX element that allows us to return multiple child elements. When we first implemented fragments in TypeScript, we didn’t have a great idea about how other libraries would utilize them. Nowadays most other libraries that encourage using JSX and support fragments have a similar API shape.
在 TypeScript 4.0 中,用户可以通过新的 jsxFragmentFactory 选项自定义片段工厂。
🌐 In TypeScript 4.0, users can customize the fragment factory through the new jsxFragmentFactory option.
例如,下面的 tsconfig.json 文件告诉 TypeScript 以与 React 兼容的方式转换 JSX,但将每个工厂调用切换为 h 而不是 React.createElement,并使用 Fragment 而不是 React.Fragment。
🌐 As an example, the following tsconfig.json file tells TypeScript to transform JSX in a way compatible with React, but switches each factory invocation to h instead of React.createElement, and uses Fragment instead of React.Fragment.
{"": {"": "esnext","": "commonjs","": "react","": "h","": "Fragment"}}
在需要对每个文件使用不同的 JSX 工厂的情况下,你可以利用新的 /** @jsxFrag */ pragma 注释。例如,以下内容…
tsxTry// Note: these pragma comments need to be written// with a JSDoc-style multiline syntax to take effect./** @jsx h *//** @jsxFrag Fragment */import {h ,Fragment } from "preact";export constHeader = (<><h1 >Welcome</h1 ></>);
……将转换为以下 JavaScript 输出……
tsxTryimport React from 'react';export const Header = (React.createElement(React.Fragment, null,React.createElement("h1", null, "Welcome")));
我们要向社区成员 Noj Vek 表示衷心的感谢,感谢他提交这个拉取请求并耐心地与我们的团队合作。
🌐 We’d like to extend a big thanks to community member Noj Vek for sending this pull request and patiently working with our team on it.
你可以查看这个拉取请求了解更多细节!
🌐 You can see that the pull request for more details!
build 模式下使用 --noEmitOnError 的速度提升
🌐 Speed Improvements in build mode with --noEmitOnError
以前,在使用 noEmitOnError 标志的情况下,针对带有错误的上一次编译在 incremental 下重新编译程序会非常慢。这是因为根据 noEmitOnError 标志,没有任何上一次编译的信息会被缓存到 .tsbuildinfo 文件中。
🌐 Previously, compiling a program after a previous compile with errors under incremental would be extremely slow when using the noEmitOnError flag.
This is because none of the information from the last compilation would be cached in a .tsbuildinfo file based on the noEmitOnError flag.
TypeScript 4.0 改变了这一点,这在这些场景中提供了极大的速度提升,从而改善了 --build 模式的场景(这意味着同时包含 incremental 和 noEmitOnError)。
🌐 TypeScript 4.0 changes this which gives a great speed boost in these scenarios, and in turn improves --build mode scenarios (which imply both incremental and noEmitOnError).
详情请阅读有关拉取请求的更多内容。
🌐 For details, read up more on the pull request.
--incremental 与 --noEmit
🌐 --incremental with --noEmit
TypeScript 4.0 允许我们在仍然利用 incremental 编译的同时使用 noEmit 标志。这在以前是不允许的,因为 incremental 需要生成 .tsbuildinfo 文件;然而,为了加快增量构建而启用这一用例足够重要,因此对所有用户都开放。
🌐 TypeScript 4.0 allows us to use the noEmit flag while still leveraging incremental compiles.
This was previously not allowed, as incremental needs to emit a .tsbuildinfo files; however, the use-case to enable faster incremental builds is important enough to enable for all users.
欲了解更多详情,你可以查看实现的拉取请求。
🌐 For more details, you can see the implementing pull request.
编辑器改进
🌐 Editor Improvements
TypeScript 编译器不仅支持大多数主流编辑器中的 TypeScript 编辑体验——它还增强了 Visual Studio 系列编辑器中的 JavaScript 使用体验,以及更多其他功能。因此,我们的大部分工作都集中在改善编辑器场景上——也就是开发者大部分时间都会使用的地方。
🌐 The TypeScript compiler doesn’t only power the editing experience for TypeScript itself in most major editors - it also powers the JavaScript experience in the Visual Studio family of editors and more. For that reason, much of our work focuses on improving editor scenarios - the place you spend most of your time as a developer.
在编辑器中使用新的 TypeScript/JavaScript 功能会因编辑器而异,但
🌐 Using new TypeScript/JavaScript functionality in your editor will differ depending on your editor, but
- Visual Studio Code 支持选择不同版本的 TypeScript。或者,你也可以使用JavaScript/TypeScript Nightly 扩展来保持使用最新版本(通常非常稳定)。
- Visual Studio 2017/2019 有 [上述 SDK 安装程序] 和 MSBuild 安装。
- Sublime Text 3 支持选择不同版本的 TypeScript
你可以查看部分支持 TypeScript 的编辑器列表,了解你喜欢的编辑器是否支持使用新版本。
🌐 You can check out a partial list of editors that have support for TypeScript to learn more about whether your favorite editor has support to use new versions.
转换为可选链
🌐 Convert to Optional Chaining
可选链是最近备受喜爱的一个特性。这也是 TypeScript 4.0 引入新的重构功能的原因,可以将常见模式转换为利用 可选链 和 空值合并!
🌐 Optional chaining is a recent feature that’s received a lot of love. That’s why TypeScript 4.0 brings a new refactoring to convert common patterns to take advantage of optional chaining and nullish coalescing!

请记住,虽然这种重构由于 JavaScript 中关于真值/假值的细微差别无法完全 精确 地捕捉相同的行为,但我们相信它应当能满足大多数使用场景的意图,尤其是在 TypeScript 对你的类型有更精确了解的情况下。
🌐 Keep in mind that while this refactoring doesn’t perfectly capture the same behavior due to subtleties with truthiness/falsiness in JavaScript, we believe it should capture the intent for most use-cases, especially when TypeScript has more precise knowledge of your types.
欲了解更多详情,请查看此功能的拉取请求。
🌐 For more details, check out the pull request for this feature.
/** @deprecated */ 支持
🌐 /** @deprecated */ Support
TypeScript 的编辑支持现在可以识别何时一个声明被标记了 /** @deprecated */ JSDoc 注释。该信息会显示在补全列表中,并作为一个建议诊断,供编辑器进行特殊处理。在像 VS Code 这样的编辑器中,已弃用的值通常会以删除线样式显示像这样。
🌐 TypeScript’s editing support now recognizes when a declaration has been marked with a /** @deprecated */ JSDoc comment.
That information is surfaced in completion lists and as a suggestion diagnostic that editors can handle specially.
In an editor like VS Code, deprecated values are typically displayed in a strike-though style like this.

这个新功能得益于Wenlu Wang的贡献。更多详情请参见拉取请求。
🌐 This new functionality is available thanks to Wenlu Wang. See the pull request for more details.
启动时的部分语义模式
🌐 Partial Semantic Mode at Startup
我们从许多用户那里听到了关于启动时间过长的抱怨,尤其是在大型项目中。罪魁祸首通常是一个叫做“程序构建”的过程。这个过程是从一组初始根文件开始,对它们进行解析,找到它们的依赖,再解析这些依赖,然后找到这些依赖的依赖,依此类推。你的项目越大,在进行基本的编辑器操作(如转到定义或快速信息)之前,你就需要等待更长的时间。
🌐 We’ve heard a lot from users suffering from long startup times, especially on bigger projects. The culprit is usually a process called program construction. This is the process of starting with an initial set of root files, parsing them, finding their dependencies, parsing those dependencies, finding those dependencies’ dependencies, and so on. The bigger your project is, the longer you’ll have to wait before you can get basic editor operations like go-to-definition or quick info.
这就是为什么我们一直在开发一种新的模式,让编辑器在完整语言服务体验加载之前,能够提供一种_部分_体验。核心理念是,编辑器可以运行一个轻量级的部分服务器,只查看编辑器当前打开的文件。
🌐 That’s why we’ve been working on a new mode for editors to provide a partial experience until the full language service experience has loaded up. The core idea is that editors can run a lightweight partial server that only looks at the current files that the editor has open.
很难准确说出你会看到什么样的改进,但根据经验,以前在 Visual Studio Code 代码库中,TypeScript 完全响应通常需要大约 20 秒到一分钟 的时间。 相比之下,我们新的部分语义模式似乎将这个延迟缩短到了仅几秒钟。 例如,在以下视频中,你可以看到两个并排的编辑器,左边运行的是 TypeScript 3.9,右边运行的是 TypeScript 4.0。
🌐 It’s hard to say precisely what sorts of improvements you’ll see, but anecdotally, it used to take anywhere between 20 seconds to a minute before TypeScript would become fully responsive on the Visual Studio Code codebase. In contrast, our new partial semantic mode seems to bring that delay down to just a few seconds. As an example, in the following video, you can see two side-by-side editors with TypeScript 3.9 running on the left and TypeScript 4.0 running on the right.
在重新启动特别大的代码库中的两个编辑器时,使用 TypeScript 3.9 的编辑器完全无法提供补全或快速信息。 另一方面,使用 TypeScript 4.0 的编辑器即使在后台加载整个项目,也能在我们正在编辑的当前文件中立即提供丰富的体验。
🌐 When restarting both editors on a particularly large codebase, the one with TypeScript 3.9 can’t provide completions or quick info at all. On the other hand, the editor with TypeScript 4.0 can immediately give us a rich experience in the current file we’re editing, despite loading the full project in the background.
目前唯一支持此模式的编辑器是 Visual Studio Code,并且在 Visual Studio Code Insiders 中将会有一些用户体验的改进。我们意识到这一体验在用户体验和功能上仍有改进空间,我们有 改进列表 供参考。我们希望获得更多关于你认为可能有用的反馈。
🌐 Currently the only editor that supports this mode is Visual Studio Code which has some UX improvements coming up in Visual Studio Code Insiders. We recognize that this experience may still have room for polish in UX and functionality, and we have a list of improvements in mind. We’re looking for more feedback on what you think might be useful.
欲了解更多信息,你可以查看原始提案、实现的拉取请求,以及后续的元问题。
🌐 For more information, you can see the original proposal, the implementing pull request, along with the follow-up meta issue.
更智能的自动导入
🌐 Smarter Auto-Imports
自动导入是一个非常棒的功能,它让编码变得更加轻松;然而,每次自动导入似乎不起作用时,都会让用户非常困扰。我们从用户那里听到的一个具体问题是,自动导入在用 TypeScript 编写的依赖上不起作用——也就是说,直到他们在项目的其他地方至少写了一个显式导入。
🌐 Auto-import is a fantastic feature that makes coding a lot easier; however, every time auto-import doesn’t seem to work, it can throw users off a lot. One specific issue that we heard from users was that auto-imports didn’t work on dependencies that were written in TypeScript - that is, until they wrote at least one explicit import somewhere else in their project.
为什么自动导入能对 @types 包起作用,但对自带类型的包不起作用呢?
事实证明,自动导入只对你的项目中 已经 包含的包有效。
因为 TypeScript 有一些奇怪的默认设置,会自动将 node_modules/@types 中的包添加到你的项目中,那些 包就会被自动导入。
另一方面,其他包被排除在外,因为遍历你所有的 node_modules 包可能会非常耗费资源。
🌐 Why would auto-imports work for @types packages, but not for packages that ship their own types?
It turns out that auto-imports only work on packages your project already includes.
Because TypeScript has some quirky defaults that automatically add packages in node_modules/@types to your project, those packages would be auto-imported.
On the other hand, other packages were excluded because crawling through all your node_modules packages can be really expensive.
所有这些,当你尝试自动导入刚安装但尚未使用的东西时,都会导致相当糟糕的入门体验。
🌐 All of this leads to a pretty lousy getting started experience for when you’re trying to auto-import something that you’ve just installed but haven’t used yet.
TypeScript 4.0 现在在编辑器场景中做了一些额外的工作,以包含你在 package.json 的 dependencies(和 peerDependencies)字段中列出的包。 这些包中的信息仅用于改进自动导入,并不会更改类型检查等其他内容。这使我们能够为所有具有类型的依赖提供自动导入,而无需进行完整的 node_modules 搜索成本。
🌐 TypeScript 4.0 now does a little extra work in editor scenarios to include the packages you’ve listed in your package.json’s dependencies (and peerDependencies) fields.
The information from these packages is only used to improve auto-imports, and doesn’t change anything else like type-checking.
This allows us to provide auto-imports for all of your dependencies that have types, without incurring the cost of a complete node_modules search.
在极少数情况下,当你的 package.json 列出超过十个尚未导入的类型依赖时,该功能会自动禁用,以防止项目加载变慢。
要强制该功能工作,或完全禁用它,你应该能够配置你的编辑器。
对于 Visual Studio Code,这是“包含 Package JSON 自动导入”(或 typescript.preferences.includePackageJsonAutoImports)设置。
🌐 In the rare cases when your package.json lists more than ten typed dependencies that haven’t been imported yet, this feature automatically disables itself to prevent slow project loading.
To force the feature to work, or to disable it entirely, you should be able to configure your editor.
For Visual Studio Code, this is the “Include Package JSON Auto Imports” (or typescript.preferences.includePackageJsonAutoImports) setting.
我们的新网站!
🌐 Our New Website!
TypeScript 网站 最近进行了全新改版并上线了!

我们已经写了一些关于我们新网站的内容,你可以去那里查看更多;不过值得一提的是,我们仍然希望听到你的想法!如果你有问题、评论或建议,你可以在网站的问题追踪器上提交。
打破变更
🌐 Breaking Changes
lib.d.ts 变更
🌐 lib.d.ts Changes
我们的 lib.d.ts 声明已经更改——尤其是 DOM 类型发生了变化。最显著的变化可能是移除了 document.origin,它只适用于旧版本的 IE 和 Safari。MDN 建议迁移到 self.origin。
🌐 Our lib.d.ts declarations have changed - most specifically, types for the DOM have changed.
The most notable change may be the removal of document.origin which only worked in old versions of IE and Safari
MDN recommends moving to self.origin.
属性覆盖访问器(反之亦然)是错误的
🌐 Properties Overriding Accessors (and vice versa) is an Error
以前,只有在使用 useDefineForClassFields 时,属性覆盖访问器或访问器覆盖属性才会报错;然而,现在当在派生类中声明会覆盖基类中 getter 或 setter 的属性时,TypeScript 总是会报错。
🌐 Previously, it was only an error for properties to override accessors, or accessors to override properties, when using useDefineForClassFields; however, TypeScript now always issues an error when declaring a property in a derived class that would override a getter or setter in the base class.
tsTryclassBase {getfoo () {return 100;}setfoo (value ) {// ...}}classDerived extendsBase {'foo' is defined as an accessor in class 'Base', but is overridden here in 'Derived' as an instance property.2610'foo' is defined as an accessor in class 'Base', but is overridden here in 'Derived' as an instance property.= 10; foo }
tsTryclassBase {prop = 10;}classDerived extendsBase {get'prop' is defined as a property in class 'Base', but is overridden here in 'Derived' as an accessor.2611'prop' is defined as a property in class 'Base', but is overridden here in 'Derived' as an accessor.() { prop return 100;}}
查看更多关于实现该拉取请求的详细信息。
🌐 See more details on the implementing pull request.
delete 的操作数必须是可选的。
🌐 Operands for delete must be optional.
在 strictNullChecks 中使用 delete 操作符时,操作数现在必须是 any、unknown、never,或是可选的(即其类型中包含 undefined)。否则,使用 delete 操作符将会导致错误。
🌐 When using the delete operator in strictNullChecks, the operand must now be any, unknown, never, or be optional (in that it contains undefined in the type).
Otherwise, use of the delete operator is an error.
tsTryinterfaceThing {prop : string;}functionf (x :Thing ) {deleteThe operand of a 'delete' operator must be optional.2790The operand of a 'delete' operator must be optional.x .prop ;}
查看更多有关实现该拉取请求的详细信息。
🌐 See more details on the implementing pull request.
TypeScript 的 Node Factory 已弃用
🌐 Usage of TypeScript’s Node Factory is Deprecated
目前 TypeScript 提供了一组用于生成 AST 节点的“工厂”函数;然而,TypeScript 4.0 引入了一个新的节点工厂 API。因此,对于 TypeScript 4.0,我们决定弃用这些旧的函数,转而使用新的函数。
🌐 Today TypeScript provides a set of “factory” functions for producing AST Nodes; however, TypeScript 4.0 provides a new node factory API. As a result, for TypeScript 4.0 we’ve made the decision to deprecate these older functions in favor of the new ones.
欲了解更多详情,请查看此更改相关的拉取请求。
🌐 For more details, read up on the relevant pull request for this change.
有关更多详情,你可以查看