你在使用哪种编程语言?快来投票,亲手选出你心目中的编程语言之王 了解详情
写点什么

TypeScript 3.6 正式发布!新增 4 项突破性特点

2019 年 9 月 03 日

TypeScript 3.6正式发布!新增4项突破性特点

8 月 28 日微软正式发布了 TypeScript 3.6 版,本文将主要介绍 TypeScript 3.6 版中的更新内容,包括:语言和编译器、新的 TypeScript Playground、编辑功能等。​


我们很高兴地宣布,TypeScript 3.6 正式面世了!


有些人可能还不太了解 TypeScript,这里做一个简单介绍:TypeScript 是一种基于 JavaScript 的语言,在后者的基础上添加了可选的静态类型;TypeScript 编译器可以检查这些类型以捕获程序中的常见错误(例如属性拼写错误和函数调用错误等);然后可以使用 TypeScript 编译器和 Babel 等工具将基于最前沿规范编写的 TypeScript 代码转换为符合标准的 ECMAScript 代码,后者可在任何浏览器或运行时(甚至是只支持 ES3 或 ES5 的旧版本)上运行。


TypeScript 不仅具备类型检查和较新的 ECMAScript 功能,其编辑工具也是业界一流水平,是 TypeScript 项目不可或缺的一部分;多种类型的编辑器为 TypeScript 提供了代码自动完成、重构和快速修复等功能。如果你在 Visual Studio 或 Visual Studio Code 中编辑过 JavaScript 文件,其实这些体验是由 TypeScript 提供的,所以你可能已经在不知情的情况下开始使用 TypeScript 了!


你可以查看TypeScript官方网站了解更多信息。TypeScript 可以通过NuGet获取,或在 npm 中键入以下命令:


npm install -g typescript
复制代码


可选的编辑器有:



不久的将来我们还会提供其他编辑器的支持


下面来看看 3.6 版中的更新内容吧!


语言和编译器

更严格的生成器

TypeScript 3.6 对迭代器和生成器函数的检查更严格了。在早期版本中,用户使用生成器时无法判断一个值是从生成器 yield 还是返回的。


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); // oops! runtime error!
复制代码


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>;    <a href="">Symbol.iterator: Generator<T, TReturn, TNext>;}</a href="">
复制代码


为了区分返回值和生成值,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); // error! 'number' 无法被分配给 'string'
复制代码


如果你更喜欢显式方法,那么还可以强制可以用 yield 表达式执行返回、yield 和计算的值的类型使用显式返回类型。下面的例子中,next( )只能用布尔值调用,并且根据 done 的值,value 可以是 string 或 number。


/** * - 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!
复制代码


这部分更改的详情可参阅github


更准确的数组扩展

在目标为 ES2015 之前的版本中,诸如 for/of 循环和数组扩展之类的结构用的发射有些复杂。因此 TypeScript 默认使用更简单的发射,只支持数组类型,并支持使用–downlevelIteration 标志在其他类型上迭代。在此标志下发出的代码更准确,但体积也大得多。


默认情况下–downlevelIteration 是关闭的,因为以 ES5 为目标的用户多数只使用带数组的迭代结构。但某些边缘情况下只支持数组的发射还是有一些可见的差异。


例如,以下示例


[...Array(5)]
复制代码


等同于下列数组:


[undefined, undefined, undefined, undefined, undefined]
复制代码


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


Array(5).slice();
复制代码


它们是有些不一样的。Array(5)生成一个长度为 5 的数组,但没有定义的属性槽!


1 in [undefined, undefined, undefined] // true1 in Array(3) // false
复制代码


当 TypeScript 调用 slice()时,它还会创建一个其索引尚未设置的数组。


这可能不太好理解,但其实有许多用户遇到了这种麻烦。TypeScript 3.6 不再使用 slice()和内置函数,而是引入了一个新的__spreadArrays助手,将 ECMAScript 2015 的内容在较旧的目标中模拟出来,不用 --downlevelIteration。 __spreadArrays 也可以在tslib中使用(如果你想缩小包的体积,那么非常值得一试)。


有关更多信息,请参阅资料


改进了 Promise 相关的用户体验

Promise 是当今最常用的异步数据方法之一。然而面向 Promise 的 API 通常会让用户感到困惑。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'的参数.// ...// Did you forget to use 'await'?}
复制代码


在 await 或.then()一个 Promise 之前就尝试访问方法也是很常见的错误。这类错误很多,我们都做了改进。


async function getCuteAnimals() {    fetch("https://reddit.com/r/aww.json")        .json()    //   ~~~~    // 属性 'json' 在类型 'Promise<Response>'上不存在.    //    // Did you forget to use 'await'?}
复制代码


这里的目的是就算用户没意识到要 await,起码这些消息能给出一些提示。


除了改进 Promise 相关的错误消息外,我们现在还在某些情况下提供了快速修复。



有关更多详细信息,请参阅原始问题及相关链接。


对标识符的 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 版本不允许在环境上下文中 get 和 set 访问器(例如在 declare-d 类中,或者在.d.ts 文件中)。理由是对于这些属性的读写而言访问器与属性没有区别;但是因为 ECMAScript 的类字段提案可能与现有 TypeScript 版本中的行为不同,我们意识到我们需要一种方法来对接这种行为差异,以便在子类中提供适当的错误。


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


declare class Foo {    // 在 3.6+ 版本中允许.    get x(): number;    set x(val: number): void;}
复制代码


在 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 的详细信息可参阅资料


新的 TypeScript Playground

应用户呼吁,新版 TypeScript Playground 做了大幅改进,引入了许多全新功能。新版 Playground 基本上是社区流行的Artem TyurinTypeScript Playground的一个 fork。我们在这里非常感谢 Artem 提供的帮助!


新版 Playground 提供了许多新选项,包括:


  • target 选项(允许用户从 es5 切换到 es3、es2015、esnext 等)

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

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


这些选项在共享 Playground 样本时也能使用,让用户可以更可靠地共享示例,无需告诉收件人“哦,别忘了打开 noImplicitAny 选项!”。


在不久的将来,我们将改进 Playground 样本功能、添加 JSX 支持、改进自动类型获取等,让用户使用 Playground 时如同在使用自己的编辑器一样。


我们欢迎大家在GitHub上提交反馈和请求!


编辑功能

分号感知代码编辑

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 变得更聪明了一些,可以先查看你现有的导入后再决定怎样自动导入其他模块。你可以在此处查看更多信息


重大更新

命名为“constructor”的类成员现在是构造函数

根据 ECMAScript 规范,使用名为 constructor 的方法的类声明现在是构造函数,无论它们是使用标识符名称还是字符串名称声明都是如此。


class C {    "constructor"() {        console.log("我是构造函数.");    }}
复制代码


注意有一个例外,就是计算属性的名称等于“constructor”的情况。


class D {    <a href="">"constructor" {        console.log("我不是一个构造函数 - 只是一个方法");    }}</a href="">
复制代码


DOM 更新

新版 lib.dom.d.ts 中的许多声明已被删除或更改。具体包括(但不限于)以下内容:


  • 全局 window 不再定义为类型 Window——而是将其定义为类型 Window&typeof globalThis。在某些情况下,最好将其类型称为 typeofwindow。

  • GlobalFetch 已经移除了,取而代之的是 WindowOrWorkerGlobalScope。

  • Navigator 上的某些非标准属性消失了。

  • experimental-webgl 上下文移除了。替代品是 webgl 或 webgl2。


JSDoc 注释不再合并

在 JavaScript 文件中,TypeScript 只会在 JSDoc 注释之前确定声明的类型。


/** * @param {string} arg *//** * oh, hi, were you trying to type something? */function whoWritesFunctionsLikeThis(arg) {    // 'arg' has type 'any'}
复制代码


关键字不能包含转义序列

以前关键字允许包含转义序列。TypeScript 3.6 中就不行了。


while (true) {    \u0063ontinue;//  ~~~~~~~~~~~~~//  error! Keywords cannot contain escape characters.}
复制代码


未来计划

想要了解官方团队未来要开展的工作,请查看今年 7 月至 12 月的半年路线图计划


我们希望这个新版本能继续改善你的开发体验。你有任何建议或遇到任何问题我们都很感兴趣,欢迎大家在GitHub上提交反馈。


英文原文:https://devblogs.microsoft.com/typescript/announcing-typescript-3-6/


2019 年 9 月 03 日 08:205494

评论

发布
暂无评论
发现更多内容

围绕“三个问题”开展的网易云音乐数据基础建设

围绕“三个问题”开展的网易云音乐数据基础建设

TypeScript 3.6正式发布!新增4项突破性特点-InfoQ