更严格的生成器(Stricter Generators)
TypeScript 3.6 为迭代器和生成器函数引入了更严格的检查。在旧版本中,生成器的使用者无法区分一个值是从生成器中 yield(产出)的还是 return(返回)的。
tsfunction* 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。
tsfunction* 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() 调用正确报错。这得益于 Iterator 和 IteratorResult 类型声明的一些更改(引入了几个新的类型参数),以及 TypeScript 用于表示生成器的一个名为 Generator 的新类型。
Iterator 类型现在允许用户指定 yield 的类型、返回的类型以及 next 可以接受的类型。
tsinterface 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 类型是一个同时具有 return 和 throw 方法且可迭代的 Iterator。
tsinterface 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)。
tstype 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 的某些用法。
tsfunction* foo() {let x: string = yield;console.log(x.toUpperCase());}let x = foo();x.next(); // first call to 'next' is always ignoredx.next(42); // error! 'number' is not assignable to 'string'
如果你更喜欢显式指定,也可以使用显式返回类型来强制执行可以从 yield 表达式返回、产出和评估的值的类型。在下文中,next() 只能使用 boolean 值调用;根据 done 的值,value 可以是 string 或 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!
有关更改的更多详细信息,请参阅此处的 pull request。
更准确的数组展开(Array Spread)
在 ES2015 之前的目标版本中,对于 for/of 循环和数组展开等结构的最高保真度编译可能会显得比较重。因此,TypeScript 默认使用一种仅支持数组类型的简化编译方式,并使用 downlevelIteration 标志来支持在其他类型上进行迭代。不带 downlevelIteration 的宽松默认值效果尚可;但是,在某些常见情况下,数组展开的转换存在可观察到的差异。例如,以下包含展开操作的数组
ts[...Array(5)];
可以重写为以下数组字面量
js[undefined, undefined, undefined, undefined, undefined];
然而,TypeScript 会将原始代码转换为如下代码
tsArray(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 关键字。
tsinterface 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 之前尝试访问其方法也很常见。这是我们可以做得更好的众多例子之一。
tsasync 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 字符的更好支持。
tsconst 𝓱𝓮𝓵𝓵𝓸 = "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 上下文中允许使用 get 和 set 访问器
在 TypeScript 的先前版本中,语言不允许在 Ambient 上下文(如 declare 类或通常在 .d.ts 文件中)中使用 get 和 set 访问器。其理由是,就读写这些属性而言,访问器与属性并没有什么区别;但是,由于 ECMAScript 的类字段提案的行为可能与现有 TypeScript 版本中的行为不同,我们意识到我们需要一种方式来传达这种差异,以便在子类中提供适当的错误提示。
因此,用户可以在 TypeScript 3.6 的 Ambient 上下文中编写 getter 和 setter。
tsdeclare 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 文件中)可以合并。这意味着现在你可以这样写
tsexport declare function Point2D(x: number, y: number): Point2D;export declare class Point2D {x: number;y: number;constructor(x: number, y: number);}
而不是必须使用
tsexport 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 构建,用户可以利用 createIncrementalProgram 和 createIncrementalCompilerHost 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 Tyurin 的 TypeScript Playground 的分支,社区成员一直在越来越多地使用它。我们非常感谢 Artem 在此提供的帮助!
新的 Playground 现在支持许多新选项,包括
target选项(允许用户从es5切换到es3、es2015、esnext等)- 所有的严格模式标志(包括仅仅
strict) - 支持纯 JavaScript 文件(使用
allowJS和可选的checkJs)
这些选项在共享 Playground 示例链接时也会持久保留,从而使用户能更可靠地共享示例,而不必告诉接收者“哦,别忘了打开 noImplicitAny 选项!”。
在不久的将来,我们将刷新 Playground 示例,增加 JSX 支持,并完善自动类型获取,这意味着你将能够在 Playground 上获得与在个人编辑器中相同的体验。
随着我们改进 Playground 和网站,我们欢迎在 GitHub 上提出反馈和 pull requests!