TypeScript 3.6
更严格的生成器
TypeScript 3.6 对迭代器和生成器函数引入了更严格的检查。 在早期版本中,生成器的使用者无法区分一个值是从生成器中 yield 还是 return 的。
function* foo() {
if (Math.random() < 0.5) yield 100;
return "Finished!";
}
let iter = foo();
let curr = iter.next();
if (curr.done) {
// TypeScript 3.5 及之前认为这是一个 'string | number'。
// 它应该知道是 'string',因为 'done' 是 'true'!
curr.value;
}此外,生成器只是假设 yield 的类型总是 any。
function* bar() {
let x: { hello(): void } = yield;
x.hello();
}
let iter = bar();
iter.next();
iter.next(123); // 哎呀!运行时错误!在 TypeScript 3.6 中,检查器现在知道在我们的第一个示例中 curr.value 的正确类型应该是 string,并且会在最后一个示例中正确地对我们调用 next() 报错。 这要归功于 Iterator 和 IteratorResult 类型声明的一些更改,这些更改引入了一些新的类型参数,以及 TypeScript 用于表示生成器的一个新类型,称为 Generator 类型。
Iterator 类型现在允许用户指定 yield 的类型、返回的类型以及 next 可以接受的类型。
interface Iterator<T, TReturn = any, TNext = undefined> {
// 接受 0 或 1 个参数 - 不接受 'undefined'
next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
return?(value?: TReturn): IteratorResult<T, TReturn>;
throw?(e?: any): IteratorResult<T, TReturn>;
}基于这项工作,新的 Generator 类型是一个 Iterator,它总是同时具有 return 和 throw 方法,并且也是可迭代的。
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 类型转换为可辨识联合类型:
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 的使用。
function* foo() {
let x: string = yield;
console.log(x.toUpperCase());
}
let x = foo();
x.next(); // 第一次调用 'next' 总是被忽略
x.next(42); // 错误!'number' 不能赋值给 'string'如果你希望显式指定,你也可以使用显式返回类型来强制 yield 表达式可以返回、yield 和求值的类型。 下面,next() 只能使用 boolean 调用,并且根据 done 的值,value 要么是 string 要么是 number。
/**
* - yields 数字
* - returns 字符串
* - 可以传入 boolean
*/
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());
// 输出:
//
// 0
// 1
// 2
// 3
// 4
// 5
// DONE!有关此更改的更多详细信息,请在此处查看拉取请求。
更准确的数组展开
在 ES2015 之前的目标中,对于像 for/of 循环和数组展开这样的结构,最忠实的输出可能会有点重。 因此,TypeScript 默认使用更简单的输出,仅支持数组类型,并通过 downlevelIteration 标志支持在其他类型上进行迭代。 没有 downlevelIteration 的更宽松的默认值工作得相当好;然而,在某些常见情况下,数组展开的转换存在可观察到的差异。 例如,包含展开的以下数组
[...Array(5)];可以重写为以下数组字面量
[undefined, undefined, undefined, undefined, undefined];但是,TypeScript 会将原始代码转换为以下代码:
Array(5).slice();这略有不同。 Array(5) 产生一个长度为 5 的数组,但没有定义的属性槽。
TypeScript 3.6 引入了一个新的 __spreadArrays 辅助函数,以准确模拟在旧目标(在 downlevelIteration 之外)中 ECMAScript 2015 的行为。 __spreadArrays 在 tslib 中也可用。
有关更多信息,请参阅相关的拉取请求。
围绕 Promise 的改进用户体验
TypeScript 3.6 对 Promise 处理不当的情况进行了一些改进。
例如,在将 Promise 传递给另一个函数之前忘记对其内容使用 .then() 或 await 是非常常见的。 TypeScript 的错误消息现在被专门化了,并告知用户也许他们应该考虑使用 await 关键字。
interface User {
name: string;
age: number;
location: string;
}
declare function getUserData(): Promise<User>;
declare function displayUser(user: User): void;
async function f() {
displayUser(getUserData());
// ~~~~~~~~~~~~~
// 类型 'Promise<User>' 的参数不能赋给类型 'User' 的参数。
// ...
// 你是否忘记使用 'await'?
}在 await 或 .then() 一个 Promise 之前尝试访问其方法也是很常见的。这是另一个我们可以做得更好的例子。
async function getCuteAnimals() {
fetch("https://reddit.com/r/aww.json").json();
// ~~~~
// 属性 'json' 在类型 'Promise<Response>' 上不存在。
//
// 你是否忘记使用 'await'?
}有关更多详细信息,请参阅原始问题,以及链接回它的拉取请求。
对标识符更好的 Unicode 支持
当输出到 ES2015 及更高版本的目标时,TypeScript 3.6 在标识符中对 Unicode 字符提供了更好的支持。
const 𝓱𝓮𝓵𝓵𝓸 = "world"; // 以前不允许,现在在 '--target es2015' 中允许SystemJS 中的 import.meta 支持
当你的 module 目标设置为 system 时,TypeScript 3.6 支持将 import.meta 转换为 context.meta。
// 此模块:
console.log(import.meta.url);
// 被转换为:
System.register([], function (exports, context) {
return {
setters: [],
execute: function () {
console.log(context.meta.url);
},
};
});在环境上下文中允许 get 和 set 访问器
在以前版本的 TypeScript 中,语言不允许在环境上下文(如在 declare 类中,或通常在 .d.ts 文件中)中使用 get 和 set 访问器。 其理由是,就写入和读取这些属性而言,访问器与属性没有区别; 然而,因为 ECMAScript 的类字段提案可能与现有版本的 TypeScript 的行为不同,我们意识到我们需要一种方式来传达这种不同的行为,以便在子类中提供适当的错误。
因此,用户可以在 TypeScript 3.6 的环境上下文中编写 getter 和 setter。
declare class Foo {
// 在 3.6+ 中允许。
get x(): number;
set x(val: number);
}在 TypeScript 3.7 中,编译器本身将利用此功能,以便生成的 .d.ts 文件也会输出 get/set 访问器。
环境类和函数可以合并
在以前版本的 TypeScript 中,在任何情况下合并类和函数都是错误。 现在,环境类和函数(带有 declare 修饰符的类/函数,或在 .d.ts 文件中)可以合并。 这意味着现在你可以编写以下代码:
export declare function Point2D(x: number, y: number): Point2D;
export declare class Point2D {
x: number;
y: number;
constructor(x: number, y: number);
}而不是需要使用
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 构建,用户可以使用 createIncrementalProgram 和 createIncrementalCompilerHost API。 用户还可以使用新暴露的 readBuilderProgram 函数从此 API 生成的 .tsbuildinfo 文件中重新水化旧的程序实例,该函数仅用于创建新程序(即,你不能修改返回的实例——它仅用于其他 create*Program 函数中的 oldProgram 参数)。
为了利用项目引用,暴露了一个新的 createSolutionBuilder 函数,它返回新类型 SolutionBuilder 的一个实例。
有关这些 API 的更多详细信息,你可以查看原始拉取请求。
分号感知的代码编辑
像 Visual Studio 和 Visual Studio Code 这样的编辑器可以自动应用快速修复、重构以及其他转换,如从其他模块自动导入值。 这些转换由 TypeScript 提供支持,旧版本的 TypeScript 会无条件地在每个语句末尾添加分号;不幸的是,这与许多用户的风格指南相悖,许多用户对编辑器插入分号感到不满。
TypeScript 现在足够智能,可以在应用此类编辑时检测你的文件是否使用分号。 如果你的文件通常没有分号,TypeScript 就不会添加分号。
有关更多详细信息,请参阅相应的拉取请求。
更智能的自动导入语法
JavaScript 有许多不同的模块语法或约定:ECMAScript 标准中的语法、Node 已支持的语法(CommonJS)、AMD、System.js 等等! 在大多数情况下,TypeScript 会默认使用 ECMAScript 模块语法进行自动导入,这在某些具有不同编译器设置的 TypeScript 项目中,或在具有纯 JavaScript 和 require 调用的 Node 项目中通常是不合适的。
TypeScript 3.6 现在在决定如何自动导入其他模块之前,会查看你现有的导入,变得更加智能。 你可以在此处的原始拉取请求中查看更多详细信息。
新的 TypeScript 演练场
TypeScript 演练场得到了急需的更新,配备了方便的新功能! 新的演练场主要是 Artem Tyurin 的 TypeScript 演练场 的一个分支,社区成员越来越多地使用它。 我们非常感谢 Artem 的帮助!
新的演练场现在支持许多新选项,包括:
target选项(允许用户从es5切换到es3、es2015、esnext等)- 所有严格性标志(包括
strict) - 对纯 JavaScript 文件的支持(使用
allowJS和可选的checkJs)
这些选项在共享演练场示例链接时也会保留,允许用户更可靠地分享示例,而不必告诉接收者“哦,别忘了打开 noImplicitAny 选项!”。
在不久的将来,我们将刷新演练场示例,添加 JSX 支持,并完善自动类型获取,这意味着你将在演练场上获得与在个人编辑器中相同的体验。
随着我们改进演练场和网站,我们欢迎在 GitHub 上提供反馈和拉取请求!