TypeScript 3.6

更严格的生成器(Stricter Generators)

TypeScript 3.6 为迭代器和生成器函数引入了更严格的检查。在旧版本中,生成器的使用者无法区分一个值是从生成器中 yield(产出)的还是 return(返回)的。

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

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 的新类型。

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

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 类型是一个同时具有 returnthrow 方法且可迭代的 Iterator

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>;
}

为了允许区分返回值和 yield 的值,TypeScript 3.6 将 IteratorResult 类型转换为可辨识联合类型(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;
}

简而言之,这意味着当直接处理来自迭代器的值时,你将能够适当地收窄这些值。

为了正确表示通过调用 next() 可以传递给生成器的类型,TypeScript 3.6 还推断了生成器函数体中 yield 的某些用法。

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 可以是 stringnumber

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!

有关更改的更多详细信息,请参阅此处的 pull request

更准确的数组展开(Array Spread)

在 ES2015 之前的目标版本中,对于 for/of 循环和数组展开等结构的最高保真度编译可能会显得比较重。因此,TypeScript 默认使用一种仅支持数组类型的简化编译方式,并使用 downlevelIteration 标志来支持在其他类型上进行迭代。不带 downlevelIteration 的宽松默认值效果尚可;但是,在某些常见情况下,数组展开的转换存在可观察到的差异。例如,以下包含展开操作的数组

ts
[...Array(5)];

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

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

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

ts
Array(5).slice();

这略有不同。Array(5) 生成一个长度为 5 的数组,但没有任何定义的属性槽位(property slots)。

TypeScript 3.6 引入了一个新的 __spreadArrays 辅助函数,以便在 downlevelIteration 之外的旧目标中准确模拟 ECMAScript 2015 中的行为。__spreadArrays 也可在 tslib 中使用。

有关更多信息,请参阅相关的 pull request

改进 Promises 的用户体验

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

例如,在将 Promise 内容传递给其他函数之前忘记 .then()await 它非常常见。TypeScript 的错误消息现在是专门定制的,会告知用户或许应该考虑使用 await 关键字。

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'?
}

await.then() 一个 Promise 之前尝试访问其方法也很常见。这是我们可以做得更好的众多例子之一。

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'?
}

有关详细信息,请参阅原始 issue 以及链接回该 issue 的 pull requests。

更好的标识符 Unicode 支持

当编译到 ES2015 及更高版本的目标时,TypeScript 3.6 包含对标识符中 Unicode 字符的更好支持。

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

SystemJS 中的 import.meta 支持

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

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);
},
};
});

Ambient 上下文中允许使用 getset 访问器

在 TypeScript 的先前版本中,语言不允许在 Ambient 上下文(如 declare 类或通常在 .d.ts 文件中)中使用 getset 访问器。其理由是,就读写这些属性而言,访问器与属性并没有什么区别;但是,由于 ECMAScript 的类字段提案的行为可能与现有 TypeScript 版本中的行为不同,我们意识到我们需要一种方式来传达这种差异,以便在子类中提供适当的错误提示。

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

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

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

Ambient 类和函数可以合并

在旧版本的 TypeScript 中,在任何情况下合并类和函数都是错误的。现在,Ambient 类和函数(带有 declare 修饰符的类/函数,或位于 .d.ts 文件中)可以合并。这意味着现在你可以这样写

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

而不是必须使用

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 合并)。

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

有关更多详细信息,请参阅 GitHub 上的原始 PR

支持 --build--incremental 的 API

TypeScript 3.0 引入了对引用其他项目并通过 --build 标志进行增量构建的支持。此外,TypeScript 3.4 引入了 incremental 标志,用于保存有关先前编译的信息,以便仅重新构建某些文件。这些标志对于更灵活地构建项目和加快构建速度非常有用。遗憾的是,使用这些标志在 Gulp 和 Webpack 等第三方构建工具中无法正常工作。TypeScript 3.6 现在公开了两组 API 来操作项目引用和增量程序构建。

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

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

有关这些 API 的更多详细信息,你可以查看原始 pull request

感知分号的代码编辑

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

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

有关更多详细信息,请参阅相应的 pull request

更智能的自动导入语法

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

TypeScript 3.6 现在更智能了,它会在决定如何自动导入其他模块之前查看你现有的导入。你可以在 这里查看原始 pull request 中的更多详细信息

新的 TypeScript Playground

TypeScript Playground 迎来了亟需的更新,并具备了便捷的新功能!新的 Playground 在很大程度上是 Artem TyurinTypeScript Playground 的分支,社区成员一直在越来越多地使用它。我们非常感谢 Artem 在此提供的帮助!

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

  • target 选项(允许用户从 es5 切换到 es3es2015esnext 等)
  • 所有的严格模式标志(包括仅仅 strict
  • 支持纯 JavaScript 文件(使用 allowJS 和可选的 checkJs

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

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

随着我们改进 Playground 和网站,我们欢迎在 GitHub 上提出反馈和 pull requests

TypeScript 文档是一个开源项目。请发送 Pull Request 来帮助我们改进这些页面 ❤

此页面的贡献者
DRDaniel Rosenwasser (51)
OTOrta Therox (12)
ELEliran Levi (1)
JBJack Bates (1)
JRUJohn Ralph Umandal (1)
3+

最后更新:2026 年 3 月 27 日