TypeScript 3.6

更严格的生成器

🌐 Stricter Generators

TypeScript 3.6 对迭代器和生成器函数引入了更严格的检查。在早期版本中,生成器的使用者无法区分一个值是从生成器中 yield 出来的还是 return 出来的。

🌐 TypeScript 3.6 introduces stricter checking for iterators and generator functions. In earlier versions, users of generators had no way to differentiate whether a value was yielded or returned from a generator.

ts
function* foo() {
if (Math.random() < 0.5) yield 100;
return "Finished!";
}
let iter = foo();
let curr = iter.next();
if (curr.done) {
// TypeScript 3.5 and prior thought this was a 'string | number'.
// It should know it's 'string' since 'done' was 'true'!
curr.value;
}

此外,生成器只是默认认为 yield 的类型一直是 any

🌐 Additionally, generators just assumed the type of yield was always any.

ts
function* bar() {
let x: { hello(): void } = yield;
x.hello();
}
let iter = bar();
iter.next();
iter.next(123); // oops! runtime error!

在 TypeScript 3.6 中,类型检查器现在知道在我们的第一个示例中 curr.value 的正确类型应该是 string,并且会在我们最后一个示例中对 next() 的调用正确地报错。这归功于在 IteratorIteratorResult 类型声明中做的一些更改,以包含一些新的类型参数,以及 TypeScript 用于表示生成器的新类型,称为 Generator 类型。

🌐 In TypeScript 3.6, the checker now knows that the correct type for curr.value should be string in our first example, and will correctly error on our call to next() in our last example. This is thanks to some changes in the Iterator and IteratorResult type declarations to include a few new type parameters, and to a new type that TypeScript uses to represent generators called the Generator type.

Iterator 类型现在允许用户指定生成的类型、返回的类型以及 next 可以接受的类型。

🌐 The Iterator type now allows users to specify the yielded type, the returned type, and the type that next can accept.

ts
interface Iterator<T, TReturn = any, TNext = undefined> {
// Takes either 0 or 1 arguments - doesn't accept 'undefined'
next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
return?(value?: TReturn): IteratorResult<T, TReturn>;
throw?(e?: any): IteratorResult<T, TReturn>;
}

在此基础上,新的 Generator 类型是一个 Iterator,它始终同时具有 returnthrow 方法,并且也是可迭代的。

🌐 Building on that work, the new Generator type is an Iterator that always has both the return and throw methods present, and is also iterable.

ts
interface Generator<T = unknown, TReturn = any, TNext = unknown>
extends Iterator<T, TReturn, TNext> {
next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
return(value: TReturn): IteratorResult<T, TReturn>;
throw(e: any): IteratorResult<T, TReturn>;
[Symbol.iterator](): Generator<T, TReturn, TNext>;
}

为了区分返回值和生成值,TypeScript 3.6 将 IteratorResult 类型转换为一个区分联合类型:

🌐 To allow differentiation between returned values and yielded values, TypeScript 3.6 converts the IteratorResult type to a discriminated union type:

ts
type IteratorResult<T, TReturn = any> =
| IteratorYieldResult<T>
| IteratorReturnResult<TReturn>;
interface IteratorYieldResult<TYield> {
done?: false;
value: TYield;
}
interface IteratorReturnResult<TReturn> {
done: true;
value: TReturn;
}

简而言之,这意味着你在直接处理迭代器中的值时,能够适当地缩小其范围。

🌐 In short, what this means is that you’ll be able to appropriately narrow down values from iterators when dealing with them directly.

为了正确表示可以从对 next() 的调用传入生成器的类型,TypeScript 3.6 还会推断生成器函数体内某些对 yield 的使用。

🌐 To correctly represent the types that can be passed in to a generator from calls to next(), TypeScript 3.6 also infers certain uses of yield within the body of a generator function.

ts
function* foo() {
let x: string = yield;
console.log(x.toUpperCase());
}
let x = foo();
x.next(); // first call to 'next' is always ignored
x.next(42); // error! 'number' is not assignable to 'string'

如果你更倾向于明确指定,也可以使用显式返回类型来强制 yield 表达式所返回、生成和评估的值的类型。下面,next() 只能用 boolean 调用,并且根据 done 的值,value 要么是 string,要么是 number

🌐 If you’d prefer to be explicit, you can also enforce the type of values that can be returned, yielded, and evaluated from yield expressions using an explicit return type. Below, next() can only be called with booleans, and depending on the value of done, value is either a string or a number.

ts
/**
* - yields numbers
* - returns strings
* - can be passed in booleans
*/
function* counter(): Generator<number, string, boolean> {
let i = 0;
while (true) {
if (yield i++) {
break;
}
}
return "done!";
}
var iter = counter();
var curr = iter.next();
while (!curr.done) {
console.log(curr.value);
curr = iter.next(curr.value === 5);
}
console.log(curr.value.toUpperCase());
// prints:
//
// 0
// 1
// 2
// 3
// 4
// 5
// DONE!

有关此更改的更多详情,请参阅此处的拉取请求

🌐 For more details on the change, see the pull request here.

更精确的数组展开

🌐 More Accurate Array Spread

在 ES2015 之前的目标环境中,对于像 for/of 循环和数组展开这样的构造,最准确的输出可能会比较笨重。因此,TypeScript 默认使用一种更简单的输出方式,仅支持数组类型,并可以通过使用 downlevelIteration 标志来支持对其他类型的迭代。没有 downlevelIteration 的宽松默认方式效果相当不错;然而,在一些常见情况下,数组展开的转换会有可观察到的差异。例如,以下包含展开的数组

🌐 In pre-ES2015 targets, the most faithful emit for constructs like for/of loops and array spreads can be a bit heavy. For this reason, TypeScript uses a simpler emit by default that only supports array types, and supports iterating on other types using the downlevelIteration flag. The looser default without downlevelIteration works fairly well; however, there were some common cases where the transformation of array spreads had observable differences. For example, the following array containing a spread

ts
[...Array(5)];

可以重写为以下数组字面量

🌐 can be rewritten as the following array literal

js
[undefined, undefined, undefined, undefined, undefined];

然而,TypeScript 会将原始代码转换为以下代码:

🌐 However, TypeScript would instead transform the original code into this code:

ts
Array(5).slice();

稍有不同。Array(5) 会生成一个长度为 5 的数组,但没有定义的属性槽。

🌐 which is slightly different. Array(5) produces an array with a length of 5, but with no defined property slots.

TypeScript 3.6 引入了一个新的 __spreadArrays 辅助工具,用于在旧版目标环境中准确模拟 ECMAScript 2015 中的行为,除了 downlevelIteration 之外。__spreadArrays 也可以在 tslib 中使用。

🌐 TypeScript 3.6 introduces a new __spreadArrays helper to accurately model what happens in ECMAScript 2015 in older targets outside of downlevelIteration. __spreadArrays is also available in tslib.

欲了解更多信息,请参见相关拉取请求 see the relevant pull request

🌐 For more information, see the relevant pull request.

改进了 Promises 的用户体验

🌐 Improved UX Around Promises

TypeScript 3.6 对 Promise 被错误处理的情况引入了一些改进。

🌐 TypeScript 3.6 introduces some improvements for when Promises are mis-handled.

例如,在将 Promise 的内容传递给另一个函数之前,忘记 .then()await 是非常常见的。TypeScript 的错误信息现在更加专业,并提示用户或许应该考虑使用 await 关键字。

🌐 For example, it’s often very common to forget to .then() or await the contents of a Promise before passing it to another function. TypeScript’s error messages are now specialized, and inform the user that perhaps they should consider using the await keyword.

ts
interface User {
name: string;
age: number;
location: string;
}
declare function getUserData(): Promise<User>;
declare function displayUser(user: User): void;
async function f() {
displayUser(getUserData());
// ~~~~~~~~~~~~~
// Argument of type 'Promise<User>' is not assignable to parameter of type 'User'.
// ...
// Did you forget to use 'await'?
}

在对 Promise 进行 await.then() 之前尝试访问其方法也是很常见的情况。这是许多例子中的另一个例子,我们有能力做得更好。

🌐 It’s also common to try to access a method before await-ing or .then()-ing a Promise. This is another example, among many others, where we’re able to do better.

ts
async function getCuteAnimals() {
fetch("https://reddit.com/r/aww.json").json();
// ~~~~
// Property 'json' does not exist on type 'Promise<Response>'.
//
// Did you forget to use 'await'?
}

欲了解更多详情,请参阅原始问题,以及链接回该问题的拉取请求。

🌐 For more details, see the originating issue, as well as the pull requests that link back to it.

更好地支持标识符的 Unicode

🌐 Better Unicode Support for Identifiers

TypeScript 3.6 在向 ES2015 及更高版本的目标平台发布时,对标识符中的 Unicode 字符提供了更好的支持。

🌐 TypeScript 3.6 contains better support for Unicode characters in identifiers when emitting to ES2015 and later targets.

ts
const 𝓱𝓮𝓵𝓵𝓸 = "world"; // previously disallowed, now allowed in '--target es2015'

import.meta 在 SystemJS 中的支持

🌐 import.meta Support in SystemJS

当你的 module 目标设置为 system 时,TypeScript 3.6 支持将 import.meta 转换为 context.meta

🌐 TypeScript 3.6 supports transforming import.meta to context.meta when your module target is set to system.

ts
// This module:
console.log(import.meta.url);
// gets turned into the following:
System.register([], function (exports, context) {
return {
setters: [],
execute: function () {
console.log(context.meta.url);
},
};
});

getset 访问器在环境上下文中是允许的

🌐 get and set Accessors Are Allowed in Ambient Contexts

在以前的 TypeScript 版本中,语言不允许在声明环境中使用 getset 访问器(例如在 declare 类中,或一般在 .d.ts 文件中)。其理由是,就对这些属性的读写操作而言,访问器与属性没有区别;然而,由于 ECMAScript 的类字段提案可能与现有 TypeScript 版本的行为不同,我们意识到需要一种方式来传达这种不同行为,以便在子类中提供相应的错误提示。

🌐 In previous versions of TypeScript, the language didn’t allow get and set accessors in ambient contexts (like in declare-d classes, or in .d.ts files in general). The rationale was that accessors weren’t distinct from properties as far as writing and reading to these properties; however, because ECMAScript’s class fields proposal may have differing behavior from in existing versions of TypeScript, we realized we needed a way to communicate this different behavior to provide appropriate errors in subclasses.

因此,用户可以在 TypeScript 3.6 的环境上下文中编写 getter 和 setter。

🌐 As a result, users can write getters and setters in ambient contexts in TypeScript 3.6.

ts
declare class Foo {
// Allowed in 3.6+.
get x(): number;
set x(val: number);
}

在 TypeScript 3.7 中,编译器本身将利用这一特性,以便生成的 .d.ts 文件也会输出 get/set 访问器。

🌐 In TypeScript 3.7, the compiler itself will take advantage of this feature so that generated .d.ts files will also emit get/set accessors.

环境类和函数可以合并

🌐 Ambient Classes and Functions Can Merge

在以前的 TypeScript 版本中,无论在任何情况下将类和函数合并都是错误的。现在,ambient 类和函数(带有 declare 修饰符的类/函数,或在 .d.ts 文件中的类/函数)可以合并。这意味着现在你可以编写如下内容:

🌐 In previous versions of TypeScript, it was an error to merge classes and functions under any circumstances. Now, ambient classes and functions (classes/functions with the declare modifier, or in .d.ts files) can merge. This means that now you can write the following:

ts
export declare function Point2D(x: number, y: number): Point2D;
export declare class Point2D {
x: number;
y: number;
constructor(x: number, y: number);
}

无需使用

🌐 instead of needing to use

ts
export interface Point2D {
x: number;
y: number;
}
export declare var Point2D: {
(x: number, y: number): Point2D;
new (x: number, y: number): Point2D;
};

这个方法的一个优点是,可调用构造函数模式可以很容易地表达,同时还允许命名空间与这些声明合并(因为 var 声明不能与 namespace 声明合并)。

🌐 One advantage of this is that the callable constructor pattern can be easily expressed while also allowing namespaces to merge with these declarations (since var declarations can’t merge with namespaces).

在 TypeScript 3.7 中,编译器将利用此功能,使得从 .js 文件生成的 .d.ts 文件能够适当地捕捉类函数的可调用性和可构造性。

🌐 In TypeScript 3.7, the compiler will take advantage of this feature so that .d.ts files generated from .js files can appropriately capture both the callability and constructability of a class-like function.

欲了解更多详情,请参考 GitHub 上的原始 PR 链接

🌐 For more details, see the original PR on GitHub.

支持 --build--incremental 的 API

🌐 APIs to Support --build and --incremental

TypeScript 3.0 引入了支持引用其他项目并使用 --build 标志增量构建它们的功能。此外,TypeScript 3.4 引入了 incremental 标志,用于保存以前编译的信息,以便仅重建某些文件。这些标志对于更灵活地结构化项目和加快构建速度非常有用。不幸的是,在使用第三方构建工具如 Gulp 和 Webpack 时,这些标志无法使用。TypeScript 3.6 现在暴露了两组 API,用于操作项目引用和增量程序构建。

🌐 TypeScript 3.0 introduced support for referencing other projects and building them incrementally using the --build flag. Additionally, TypeScript 3.4 introduced the incremental flag for saving information about previous compilations to only rebuild certain files. These flags were incredibly useful for structuring projects more flexibly and speeding builds up. Unfortunately, using these flags didn’t work with 3rd party build tools like Gulp and Webpack. TypeScript 3.6 now exposes two sets of APIs to operate on project references and incremental program building.

对于创建 incremental 构建,用户可以利用 createIncrementalProgramcreateIncrementalCompilerHost API。 用户还可以使用新公开的 readBuilderProgram 函数从此 API 生成的 .tsbuildinfo 文件重新加载旧的程序实例,该函数仅用于创建新程序(即你不能修改返回的实例——它仅用于在其他 create*Program 函数中作为 oldProgram 参数使用)。

🌐 For creating incremental builds, users can leverage the createIncrementalProgram and createIncrementalCompilerHost APIs. Users can also re-hydrate old program instances from .tsbuildinfo files generated by this API using the newly exposed readBuilderProgram function, which is only meant to be used as for creating new programs (i.e. you can’t modify the returned instance - it’s only meant to be used for the oldProgram parameter in other create*Program functions).

为了利用项目引用,已经公开了一个新的 createSolutionBuilder 函数,该函数返回新类型 SolutionBuilder 的实例。

🌐 For leveraging project references, a new createSolutionBuilder function has been exposed, which returns an instance of the new type SolutionBuilder.

有关这些 API 的更多详情,你可以查看原始拉取请求

🌐 For more details on these APIs, you can see the original pull request.

支持分号的代码编辑

🌐 Semicolon-Aware Code Edits

像 Visual Studio 和 Visual Studio Code 这样的编辑器可以自动应用快速修复、重构和其他转换操作,比如自动从其他模块导入值。这些转换由 TypeScript 提供支持,而旧版本的 TypeScript 无条件地在每条语句的末尾添加分号;不幸的是,这与许多用户的风格指南不一致,导致许多用户对编辑器自动插入分号感到不满。

🌐 Editors like Visual Studio and Visual Studio Code can automatically apply quick fixes, refactorings, and other transformations like automatically importing values from other modules. These transformations are powered by TypeScript, and older versions of TypeScript unconditionally added semicolons to the end of every statement; unfortunately, this disagreed with many users’ style guidelines, and many users were displeased with the editor inserting semicolons.

TypeScript 现在已经足够智能,可以在应用这类编辑时检测你的文件是否使用分号。 如果你的文件通常没有分号,TypeScript 不会添加分号。

🌐 TypeScript is now smart enough to detect whether your file uses semicolons when applying these sorts of edits. If your file generally lacks semicolons, TypeScript won’t add one.

欲了解更多详情,请参阅相应的拉取请求

🌐 For more details, see the corresponding pull request.

更智能的自动导入语法

🌐 Smarter Auto-Import Syntax

JavaScript 有很多不同的模块语法或约定:ECMAScript 标准中的,Node 已经支持的(CommonJS),AMD,System.js,以及更多! 在大多数情况下,TypeScript 默认会使用 ECMAScript 模块语法进行自动导入,但在某些具有不同编译器设置的 TypeScript 项目中,或在使用普通 JavaScript 和 require 调用的 Node 项目中,这通常是不合适的。

🌐 JavaScript has a lot of different module syntaxes or conventions: the one in the ECMAScript standard, the one Node already supports (CommonJS), AMD, System.js, and more! For the most part, TypeScript would default to auto-importing using ECMAScript module syntax, which was often inappropriate in certain TypeScript projects with different compiler settings, or in Node projects with plain JavaScript and require calls.

TypeScript 3.6 在决定如何自动导入其他模块之前,现在会更聪明地查看你已有的导入。你可以在这里查看原始拉取请求的更多详情

🌐 TypeScript 3.6 is now a bit smarter about looking at your existing imports before deciding on how to auto-import other modules. You can see more details in the original pull request here.

新的 TypeScript Playground

🌐 New TypeScript Playground

TypeScript Playground 进行了急需的刷新,并增加了许多实用的新功能! 新的 Playground 很大程度上是 Artem TyurinTypeScript Playground 的一个分支,社区成员越来越多地在使用它。 我们要感谢 Artem 在这里提供的帮助!

🌐 The TypeScript playground has received a much-needed refresh with handy new functionality! The new playground is largely a fork of Artem Tyurin’s TypeScript playground which community members have been using more and more. We owe Artem a big thanks for helping out here!

新的 Playground 现在支持许多新选项,包括:

🌐 The new playground now supports many new options including:

  • [target](/tsconfig#target) 选项(允许用户从 es5 切换到 es3es2015esnext 等)
  • 所有严格模式标志(包括仅 strict
  • 支持普通 JavaScript 文件(使用 allowJS,可选使用 checkJs

这些选项在共享 Playground 示例链接时也会保留,使用户能够更可靠地分享示例,而无需告诉接收者“哦,别忘了打开 noImplicitAny 选项!”。

🌐 These options also persist when sharing links to playground samples, allowing users to more reliably share examples without having to tell the recipient “oh, don’t forget to turn on the noImplicitAny option!“.

在不久的将来,我们将更新 Playground 示例,添加 JSX 支持,并完善自动类型获取功能,这意味着你将能够在 Playground 上获得与个人编辑器相同的体验。

🌐 In the near future, we’re going to be refreshing the playground samples, adding JSX support, and polishing automatic type acquisition, meaning that you’ll be able to see the same experience on the playground as you’d get in your personal editor.

在我们改进游乐场和网站的同时,欢迎在 GitHub 上提供反馈和提交请求

🌐 As we improve the playground and the website, we welcome feedback and pull requests on GitHub!