当前位置:   article > 正文

元组中数组_TypeScript 4.0发布,新增可变参数元组类型等

the operand of a 'delete' operator must be optional.

昨天微软TypeScript宣布发布TypeScript 4.0版本。作为一个大版本发布,大家一定很关心其带来的变化和改善。请和虫虫一起探索。

91f3bde6c18fab3a5fbfc9cc825ba512.png

概述

TypeScript 是现代JavaScript生态圈核心部分,是有程序员大神,安德斯.哈尔斯伯格,在继Delphi,J++,.net和C++之后又一个语言。是微软奉献给开源社区的神器之一。在npm上,在上个月,2020年7月TypeScript首次突破了每月超过5000万的下载量。

StackOverflow的最新开发人员调查中,TypeScript定位为第二受欢迎的语言(第一是Rust)。

106a10bac640eeb3e9acd4838df914e4.png

在最新的JS调查状况中,大约89%的使用TypeScript的开发人员表示会再次使用它。

TypeScript是一种通过添加静态类型的语法在JavaScript之上构建的语言。这个想法是,通过记下值的类型以及它们的使用位置,可以使用TypeScript对代码进行类型检查,并在运行代码之前(甚至在保存文件之前)获得有关错误的信息。还可以使用TypeScript编译器从代码中剥离类型,并提供可在任何地方运行的简洁易读的JavaScript。

除了检查之外,TypeScript还使用静态类型来支持强大的编辑器工具,例如自动完成,代码导航,重构等。在Visual Studio Code或Visual Studio等编辑器中使用过JavaScript,那么已经在使用由类型和TypeScript提供支持的体验。

大版本变化总结

自从3.0开始TS发生了很多变化,首先TypeScript 3.0本身带来大量改善,统一元组类型和参数列表是一大亮点,可在函数上启用大量现有的JavaScript模式。该发行版还提供了项目参考,以帮助扩展,组织和共享代码库。产生重大影响的一个小变化是3.0引入了类型安全的替代,any称为unknown。

TypeScript 3.1扩展了映射类型的功能以处理元组和数组类型,并极大地简化了将属性附加到函数的过程,而无需诉诸于已失效的TypeScript特定运行时功能。

TypeScript 3.2 允许泛型类型的对象,并通过严格的输入leveraged 3.0,以更好的模型元编程与功能bind,call以及apply相结合。

TypeScript 3.3在3.2之后遵循一些稳定性,但是在使用联合类型方法时还带来了生活质量的改进,并在模式下增加了文件增量构建。--build

TypeScript 3.4,进一步倾向于支持功能模式,更好地支持不可变数据结构,并改进了对高阶通用函数的推断。最大的亮点是引入了flag,它是一种避免每次运行TypeScript都完全重建而无需项目引用的方法,从而可以更快地进行编译和类型检查。--incremental

TypeScript 3.5和3.6,加强了一些类型系统规则,以及更智能的兼容性检查规则。

TypeScript 3.7是一个非常值得关注的版本,因为它具有新类型系统功能与ECMAScript功能的丰富结合。在类型系统方面,引入了递归类型别名引用和对断言样式函数的支持,这两者都是独特的类型系统功能。从JavaScript来看,该版本带来了可选的链接和合并功能,这是TypeScript和JavaScript用户最迫切的两项功能。

,3.8和3.9带来了仅类型的导入/导出,以及ECMAScript功能,例如私有字段,await模块中的顶层以及新语法。这些版本还提供了性能和可伸缩性优化。

4.0新功能概述

如果已经在项目中使用TypeScript,则可以通过NuGet获取它,也可以通过以下命令使用npm:

npm install -D typescript

也可以通通VS 2019/2017和VSC版本内置获得。

可变参数元组类型

想象JavaScript中的一个函数,该函数concat采用两个数组或元组类型并将它们连接在一起以创建一个新数组。

function concat(arr1, arr2) {return [...arr1, ...arr2];}

还有tail,它接受一个数组或元组,并返回除第一个元素外的所有元素。

function tail(arg) {const [_, ...result] = arg;return result}

如何在TypeScript中键入其中一个?

对于concat,在较旧版本的语言中唯一可以做的就是编写重载。

function concat(arr1: [], arr2: []): [];function concat<A>(arr1: [A], arr2: []): [A];function concat<A, B>(arr1: [A, B], arr2: []): [A, B];function concat<A, B, C>(arr1: [A, B, C], arr2: []): [A, B, C];function concat<A, B, C, D>(arr1: [A, B, C, D], arr2: []): [A, B, C, D];function concat<A, B, C, D, E>(arr1: [A, B, C, D, E], arr2: []): [A, B, C, D, E];function concat<A, B, C, D, E, F>(arr1: [A, B, C, D, E, F], arr2: []): [A, B, C, D, E, F];)

第二个数组始终为空时的七个重载。arr2有一个参数的情况:

function concat<A2>(arr1: [], arr2: [A2]): [A2];function concat<A1, A2>(arr1: [A1], arr2: [A2]): [A1, A2];function concat<A1, B1, A2>(arr1: [A1, B1], arr2: [A2]): [A1, B1, A2];function concat<A1, B1, C1, A2>(arr1: [A1, B1, C1], arr2: [A2]): [A1, B1, C1, A2];function concat<A1, B1, C1, D1, A2>(arr1: [A1, B1, C1, D1], arr2: [A2]): [A1, B1, C1, D1, A2];function concat<A1, B1, C1, D1, E1, A2>(arr1: [A1, B1, C1, D1, E1], arr2: [A2]): [A1, B1, C1, D1, E1, A2];function concat<A1, B1, C1, D1, E1, F1, A2>(arr1: [A1, B1, C1, D1, E1, F1], arr2: [A2]): [A1, B1, C1, D1, E1, F1, A2];

很明显,这已经变得不合理了。不幸的是,键入像这样的函数也会遇到tail同样的问题。

还有另外一种情况,被称为"一千个重载死亡",它甚至不能解决一般问题。它只为想写的重载给出正确的类型。如果想做一个包罗万象的案例,则需要像下面这样的重载:

function concat<T, U>(arr1: T[], arr2: U[]): Array<T | U>;

但是,使用元组时,该签名不会对输入的长度或元素的顺序进行任何编码。

TypeScript 4.0带来了两个基本改善,并在推理方面进行了改进,使键入这些内容成为可能。

第一个变化是元组类型语法中的传播现在可以通用。这样即使不知道要操作的实际类型,也可以表示元组和数组的高阶操作。当在这些元组类型中实例化通用扩展时(或用实型替换),它们可以产生其他数组和元组类型集。

例如,可以在TS 4.0中键入像这样的函数tail,而不会出现"一千个重载死亡"的问题。

function tail<T extends any[]>(arr: readonly [any, ...T]) {const [_ignored, ...rest] = arr;return rest;}const myTuple = [1, 2, 3, 4] as const;const myArray = ["hello", "world"];// type [2, 3, 4]const r1 = tail(myTuple);// type [2, 3, 4, ...string[]]const r2 = tail([...myTuple, ...myArray] as const);

第二个变化是,其余元素可以出现在元组中的任何位置,而不仅仅是在结尾。

type Strings = [string, string];type Numbers = [number, number];// [string, string, number, number, boolean]type StrStrNumNumBool = [...Strings, ...Numbers, boolean];

之前,TypeScript会发出如下错误:

A rest element must be last in a tuple type.

TypeScript 4.0可以放宽此限制。

请注意,在没有已知长度的类型的情况下,结果类型也将变得不受限制,并且所有以下所有元素都将成为结果其余元素类型。

type Strings = [string, string];type Numbers = number[]// [string, string, ...Array]type Unbounded = [...Strings, ...Numbers, boolean];

通过将这两种功能结合在一起,就可以很便捷的编写一个类型正确的签名

concat:type Arr = readonly any[];function concat<T extends Arr, U extends Arr>(arr1: T, arr2: U): [...T, ...U] {return [...arr1, ...arr2];}

尽管一个签名仍然有些冗长,但这只是一个签名,不必重复,它可以在所有数组和元组上提供可预测的行为。

功能本身很棒,但在更复杂的场景中也很有用。例如,函数partialCall的部分应用名为的参数。partialCall带有一个函数f以及几个初始的期望参数,返回一个新函数,该函数接受f仍需要的任何其他参数,并f在接收到它们时进行调用。

function partialCall(f, ...headArgs) {return (...tailArgs) => f(...headArgs, ...tailArgs)}

TS 4.0改进了rest参数和rest tuple元素的推断过程,使其"正常工作":

type Arr = readonly unknown[];function partialCall<T extends Arr, U extends Arr, R>(f: (...args: [...T, ...U]) => R, ...headArgs: T) {return (...tailArgs: U) => f(...headArgs, ...tailArgs)}

partialCall了解其初始可以使用和不能使用的参数,并返回可以接受和拒绝的函数。

const foo = (x: string, y: number, z: boolean) => {}// This doesn't work because we're feeding in the wrong type for 'x'.const f1 = partialCall(foo, 100);// ~~~// error! Argument of type 'number' is not assignable to parameter of type 'string'.// This doesn't work because we're passing in too many arguments.const f2 = partialCall(foo, "hello", 100, true, "oops")// ~~~~~~// error! Expected 4 arguments, but got 5.// This works! It has the type '(y: number, z: boolean) => void'const f3 = partialCall(foo, "hello");// What can we do with f3 now?f3(123, true); // works!f3();// error! Expected 2 arguments, but got 0.f3(123, "hello");// ~~~~~~~// error! Argument of type 'string' is not assignable to parameter of type 'boolean'.

可变参数元组类型启用了许多新的令人兴奋的模式,尤其是在函数组成方面,可以利用它来做更好的工作,以检查JavaScript的内置bind方法的类型。

元组元素标签

改善元组类型和参数列表非常重要,围绕常见的JavaScript习惯用法进行强类型验证。实际上只是对参数列表进行切片和切块并将它们传递给其他函数。可以使用元组类型作rest参数的想法很有意义。

例如,以下使用元组类型作为参数的函数

function foo(...args: [string, number]): void {// ...}…should appear no different from the following functionfunction foo(arg0: string, arg1: number): void {// ...}…for any caller of foo.foo("hello", 42); // worksfoo("hello", 42, true); // errorfoo("hello"); // error

差异有:可读性。在第一个示例中,没有第一个和第二个元素的参数名称。尽管这些对类型检查没有影响,但元组位置上缺少标签会使其更难使用,难以传达意图。

这就是为什么要在TypeScript 4.0中,元组类型需要提供标签了。

type Range = [start: number, end: number];

为了加深参数列表和元组类型之间的联系,其余元素和可选元素的语法与参数列表的语法相同。

type Foo = [first: number, second?: string, ...rest: any[]];

使用带标签的元组时有一些规则。例如,当标记一个元组元素时,还必须标记该元组中的所有其他元素。

type Bar = [first: string, number];// ~~~~~~// error! Tuple members must all have names or all not have names.

值得注意的是,标签不需要在解构时以不同的方式命名变量。它们纯粹是那里的文档和工具。

function foo(x: [first: string, second: number]) {// ...// note: we didn't need to name these 'first' and 'second'let [a, b] = x;// ...}

总的来说,带标签的元组在利用元组和参数列表周围的模式以及以类型安全的方式实现重载时很方便。实际上,TypeScript的编辑器支持会在可能的情况下尝试将它们显示为重载。

构造函数的类属性推断

当noImplicitAny启用时,TypeScript 4.0可以使用控制流分析来确定类中的属性类型。

class Square {// Previously: implicit any!// Now: inferred to `number`!area;sideLength;constructor(sideLength: number) {this.sideLength = sideLength;this.area = sideLength ** 2;}}

如果并非将构造函数的所有路径都分配给实例成员,则该属性可能被认为是undefined。

class Square {sideLength;constructor(sideLength: number) {if (Math.random()) {this.sideLength = sideLength;}}get area() {return this.sideLength ** 2;// ~~~~~~~~~~~~~~~// error! Object is possibly 'undefined'.}}

短路分配运算符

JavaScript和许多其他语言一样支持一组称为复合赋值运算符的运算符。复合赋值运算符将一个运算符应用于两个参数,然后将结果赋给左侧。比如:

// Additiona += b;a -= b;a *= b;a /= b;a **= b;a <<= b;

JavaScript中的许多运算符都有对应的赋值运算符!但是仍然存在三个值得注意的例外操作符:逻辑和(&&),逻辑或(||)和无效合并(??)。

TS 4.0新引入一个新的ECMAScript功能添加三个新的赋值操作符:&&=,||=,和??=。

这些运算符非常适合替换用户可能编写如下代码的任何示例:

a = a && b;a = a || b;a = a ?? b;或if// could be 'a ||= b'if (!a) {a = b;}

甚至一些模式支持惰性初始化,只有在需要它们时才加载。

let values: string[];// 之前(values ?? (values = [])).push("hello");// TS 4.0(values ??= []).push("hello");

在极少数情况下使用带有副作用的getter或setter时,值得注意的是这些运算符仅在必要时执行赋值。从这个意义上讲,不仅操作符的右侧"短路"了,赋值本身也是如此。

obj.prop ||= foo();// roughly equivalent to either of the followingobj.prop || (obj.prop = foo());if (!obj.prop) {obj.prop = foo();}

catch语句中绑定unknown

自TypeScript诞生以来,catch子句变量始终为any。TypeScript允许对它们进行任何操作。

try {// ...}catch (x) {// x has type 'any' - have fun!console.log(x.message);console.log(x.toUpperCase());x++;x.yadda.yadda.yadda();}

如果试图防止在错误处理代码中发生更多错误,则上面的代码会有一些不良行为!因为这些变量any默认情况下具有类型,所以它们缺少类型安全性,这些类型安全性可能在无效操作上出错。

TypeScript 4.0允许指定catch子句变量的类型unknown。unknown比any它更安全,在对值进行运算之前,需要执行某种类型检查。

try {// ...}catch (e: unknown) {// error!// Property 'toUpperCase' does not exist on type 'unknown'.console.log(e.toUpperCase());if (typeof e === "string") {// works!// We've narrowed 'e' down to the type 'string'.console.log(e.toUpperCase());}}

尽管catch默认情况下变量的类型不会更改,但将来可能会考虑使用新的模式标志,以便用户可以选择这种行为。同时,应该可以编写lint规则来强制变量具有或的显式注释。

定制JSX工厂

使用JSX时,片段是JSX元素的一种,它允许返回多个子元素。大多数鼓励使用JSX和支持片段的其他库都具有类似的API形状。

TypeScript 4.0中,用户可以通过新jsxFragmentFactory选项来自定义片段工厂。

例如,下面代码实现和React兼容的方式转换JSX,但将每个工厂调用切换为而不是,并使用代替。

{"compilerOptions": {"target": "esnext","module": "commonjs","jsx": "react","jsxFactory": "h","jsxFragmentFactory": "Fragment"}}

如果需要基于每个文件使用不同的JSX,则可以利用新的/** @jsxFrag */注释。例如,

import { h, Fragment } from "preact";let stuff = <>
Hello
>;/** @jsxFrag Fragment */import { h, Fragment } from "preact";let stuff = h(Fragment, null,h("div", null, "Hello"));

build模式下的速度改进--noEmitOnError

以前,使用该标志时,在先前编译时出现错误而导致的程序之后编译程序将非常慢。这是因为来自上一次编译的任何信息都不会基于该标志被缓存在文件中。--incremental--noEmitOnError.tsbuildinfo--noEmitOnError

TypeScript 4.0对此进行了更改,从而在这些情况下极大地提高了速度,并进而改善了模式情况(这意味着和)。

--incremental与--noEmit

TypeScript 4.0允许在利用编译时使用该标志。以前不允许这样做。但是,启用更快的增量构建的用例足够重要,可以为所有用户启用。

编辑器改进

TypeScript编译器不仅可以为大多数主要编辑器提供TypeScript本身的编辑体验,还可以为Visual Studio系列编辑器等提供JavaScript体验。根据编辑器,在编辑器中使用新的TypeScript/JavaScript功能会有所不同,但是

Visual Studio Code支持选择不同版本的TypeScript。另外,还有JavaScript /TypeScript Nightly Extension可以保持最新发展(通常非常稳定)。

Visual Studio 2017/2019具有上述SDK安装程序和MSBuild安装程序。

转换为可选链接

可选链接是一项新功能,受到了广泛的欢迎。这就是为什么TypeScript 4.0带来了新的重构来转换常见模式以利用可选链接和无效合并的原因!

Converting a && a.b.c && a.b.c.d.e.f() to a?.b.c?.d.e.f.(), image

请记住,尽管由于JavaScript中真实性/虚假性的微妙之处,这种重构不能完美地捕获相同的行为,但它应该捕获大多数用例的意图,尤其是当TypeScript对类型有更精确的了解时。

/** @deprecated */支持

TypeScript的编辑支持现在可以识别使用/** @deprecated *JSDoc注释标记的声明。该信息显示在完成列表中,并作为编辑器可以特别处理的建议诊断。在VS Code这样的编辑器中,不建议使用的值通常显示为"触击式"样式。

2bc3c2f56ed5c8a6c1b2a977c67b01a9.png

启动时的部分语义模式

在大型项目中程序构建的过程从初始的一组根文件开始,进行解析,查找其依赖项,解析这些依赖项,查找那些依赖项的依赖项,等等。项目越大,获得基本的编辑器操作(如转到定义或快速信息)的时间就越长。

编辑器可以运行轻量级的部分服务器,该服务器仅查看编辑器已打开的当前文件。

很难确切地说出将看到什么样的改进,但有趣的是,TypeScript在Visual Studio Code代码库上变得完全响应之前,通常需要20秒到一分钟的时间。相比之下,新的部分语义模式似乎将延迟降低了几秒钟。

23d9da37a56c6bbffda4a318fa0d4639.png

在特别大的代码库上重新启动两个编辑器时,带有TypeScript 3.9的编辑器根本无法提供完成功能或快速信息。另一方面,尽管在后台加载了整个项目,但具有TypeScript 4.0的编辑器可以立即为正在编辑的当前文件提供丰富的体验。

当前,唯一支持此模式的编辑器是Visual Studio Code,Visual Studio Code Insiders中将对UX进行一些改进。

更智能的自动导入

自动导入是一个了不起的功能,它使编码变得容易得多。之前自动导入对用TypeScript编写的依赖项不起作用。需要在项目的其他位置至少写了一个明确的导入。

为什么自动导入适用于@types软件包,但不适用于运送自己类型的软件包?事实证明,自动导入仅适用于项目已包含的软件包。由于TypeScript具有一些古怪的默认值,这些默认值会自动将软件包添加到项目中,因此这些软件包将被自动导入。另一方面,其他软件包被排除在外,因为在所有软件包中进行爬网确实非常昂贵

当尝试自动导入刚刚安装但尚未使用的某些内容时,所有这些都会导致非常糟TS4.0现在确实在编辑情景一些额外的工作,包括已经在你列出的软件包的(和)领域。这些包中的信息仅用于改进自动导入,不会更改类型检查等其他内容。能够为所有具有类型的依赖项提供自动导入,而不会产生完整搜索的费用。

在极少数情况下,当列出尚未导入的十多个类型化依赖项时,此功能会自动禁用自身,以防止缓慢的项目加载。要强制功能正常运行或完全禁用它,应该能够配置编辑器。对于Visual Studio Code,这是"包含包JSON自动导入"(或)设置。

145123a9b971137924f9f9f8492cee78.png

新站点

TypeScript网站最近被彻底重写并推出!

37138345dd490b641c66d71906d5f1ba.png

网站中提供了一些关于新站点的文章,因此可以在此处阅读更多内容。但值得一提的是,

其他变化

lib.d.ts 变化

DOM的类型已更改。最值得注意的lib.d.ts变化可能是仅在IE和Safari MDN建议改用的旧版本中才有效。

属性覆盖访问器错误

以前,使用useDefineForClassFields; 时属性覆盖访问者或访问器覆盖属性只是错误。但是,TypeScript现在在派生类中声明将覆盖基类中的getter或setter的属性时总是发出错误。

class Base {get foo() {return 100;}set foo() {// ...}}class Derived extends Base {foo = 10;// ~~~// error!// 'foo' is defined as an accessor in class 'Base',// but is overridden here in 'Derived' as an instance property.}class Base {prop = 10;}class Derived extends Base {get prop() {// ~~~~// error!// 'prop' is defined as a property in class 'Base', but is overridden here in 'Derived' as an accessor.return 100;}}

delete的操作数必须可选

当使用delete在操作strictNullChecks,操作数现在必须是any,unknown,never,或者是可选的(因为它包含undefined的类型)。否则,使用delete运算符会提示错误的。

interface Thing {prop: string;}function f(x: Thing) {delete x.prop;// ~~~~~~// error! The operand of a 'delete' operator must be optional.}

TypeScript的Node Factory

TypeScript提供了一组用于生成AST节点的"工厂"函数;但是,TypeScript 4.0提供了新的节点工厂API。结果, TypeScript 4.0,推荐使用这些新功能。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/305611
推荐阅读
相关标签
  

闽ICP备14008679号