我们很高兴地宣布,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
可选的编辑器有:
-
Visual Studio Code的说明。
不久的将来我们还会提供其他编辑器的支持。
下面来看看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 Tyurin的TypeScript 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/