TypeScript 3.6

更严格的生成器

¥Stricter Generators

TypeScript 3.6 对迭代器和生成器函数进行了更严格的检查。在早期版本中,生成器的用户无法区分一个值是生成器产生的还是返回的。

¥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 的值,valuestring 还是 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 默认使用更简单的 emit,它仅支持数组类型,并支持使用 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 以外的旧目标平台上发生的情况。__spreadArraystslib 中也可用。

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

更多信息请见 查看相关的拉取请求

¥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 类中,或通常在 .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 中,任何情况下合并类和函数都是错误的。现在,环境类和函数(带有 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 很大程度上是 Artem TyurinTypeScript 体验区 的一个分支,社区成员越来越多地使用它。非常感谢 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 选项(允许用户从 es5 切换到 es3es2015esnext 等)

    ¥The target option (allowing users to switch out of es5 to es3, es2015, esnext, etc.)

  • 所有严格性标志(包括 strict

    ¥All the strictness flags (including just strict)

  • 支持纯 JavaScript 文件(使用 allowJS 和可选的 checkJs

    ¥Support for plain JavaScript files (using allowJS and optionally 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.

随着我们不断改进 Playground 和网站,我们欢迎在 GitHub 上提出反馈和拉取请求

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