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>;
    Symbol.iterator: Generator<T, TReturn, TNext>;
}

为了区分返回值和生成值,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] // true
1 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 {
    "constructor" {
        console.log("我不是一个构造函数 - 只是一个方法");
    }
}

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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章