Skip to content
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

TypeScript 3.6

更严格的生成器

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 及之前认为这是一个 'string | number'。
  // 它应该知道是 'string',因为 'done' 是 'true'!
  curr.value;
}

此外,生成器只是假设 yield 的类型总是 any

ts
function* bar() {
  let x: { hello(): void } = yield;
  x.hello();
}

let iter = bar();
iter.next();
iter.next(123); // 哎呀!运行时错误!

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

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

ts
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,它总是同时具有 returnthrow 方法,并且也是可迭代的。

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 类型转换为可辨识联合类型:

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(); // 第一次调用 'next' 总是被忽略
x.next(42); // 错误!'number' 不能赋值给 'string'

如果你希望显式指定,你也可以使用显式返回类型来强制 yield 表达式可以返回、yield 和求值的类型。 下面,next() 只能使用 boolean 调用,并且根据 done 的值,value 要么是 string 要么是 number

ts
/**
 * - 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 的更宽松的默认值工作得相当好;然而,在某些常见情况下,数组展开的转换存在可观察到的差异。 例如,包含展开的以下数组

ts
[...Array(5)];

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

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

但是,TypeScript 会将原始代码转换为以下代码:

ts
Array(5).slice();

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

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

有关更多信息,请参阅相关的拉取请求

围绕 Promise 的改进用户体验

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());
  //              ~~~~~~~~~~~~~
  // 类型 'Promise<User>' 的参数不能赋给类型 'User' 的参数。
  //   ...
  // 你是否忘记使用 'await'?
}

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

ts
async function getCuteAnimals() {
  fetch("https://reddit.com/r/aww.json").json();
  //   ~~~~
  // 属性 'json' 在类型 'Promise<Response>' 上不存在。
  //
  // 你是否忘记使用 'await'?
}

有关更多详细信息,请参阅原始问题,以及链接回它的拉取请求。

对标识符更好的 Unicode 支持

当输出到 ES2015 及更高版本的目标时,TypeScript 3.6 在标识符中对 Unicode 字符提供了更好的支持。

ts
const 𝓱𝓮𝓵𝓵𝓸 = "world"; // 以前不允许,现在在 '--target es2015' 中允许

SystemJS 中的 import.meta 支持

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

ts
// 此模块:

console.log(import.meta.url);

// 被转换为:

System.register([], function (exports, context) {
  return {
    setters: [],
    execute: function () {
      console.log(context.meta.url);
    },
  };
});

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

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

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

ts
declare class Foo {
  // 在 3.6+ 中允许。
  get x(): number;
  set x(val: number);
}

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

环境类和函数可以合并

在以前版本的 TypeScript 中,在任何情况下合并类和函数都是错误。 现在,环境类和函数(带有 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 的更多详细信息,你可以查看原始拉取请求

分号感知的代码编辑

像 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 TyurinTypeScript 演练场 的一个分支,社区成员越来越多地使用它。 我们非常感谢 Artem 的帮助!

新的演练场现在支持许多新选项,包括:

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

这些选项在共享演练场示例链接时也会保留,允许用户更可靠地分享示例,而不必告诉接收者“哦,别忘了打开 noImplicitAny 选项!”。

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

随着我们改进演练场和网站,我们欢迎在 GitHub 上提供反馈和拉取请求