今天,微软宣布 TypeScript 4.0 正式版上线了!这一新版本深入改进了表现力、生产力和可伸缩性,是 TypeScript 语言的新一代版本。
今天,我们很高兴地宣布 TypeScript 4.0 正式版终于上线了!它是我们深入改进表现力、生产力和可伸缩性的结果,是 TypeScript 语言的新一代版本。
如果你还不熟悉 TypeScript,这里简单介绍一下:它是一种在 JavaScript 之上通过添加静态类型语法来构建的语言。它的基本理念是,记下值的类型以及它们的使用位置后,可以使用 TypeScript 对代码进行类型检查,并在运行代码之前(甚至在保存文件之前)告诉你代码错误的相关信息。然后,你可以使用 TypeScript 编译器从代码中剥离类型,并为你提供可在任何地方运行的简洁易读的 JavaScript 代码。除了类型检查之外,TypeScript 还使用静态类型来支持强大的编辑器工具,例如自动完成、代码导航、重构等。实际上,如果你在 Visual Studio Code 或 Visual Studio 这样的编辑器中使用过 JavaScript,那么你已经用上了类型和 TypeScript 带来的体验。可以在我们的网站上了解更多相关信息。
TypeScript 4.0 没有引入特别重大的更改。实际上,如果你刚刚开始接触这种语言,那么现在是开始使用它的最佳时机。它的社区已经成熟完善,并在不断发展,拥有可运行的代码和很棒的新资源可供学习。还有一件事情:尽管我们为 4.0 引入了那么多好东西,但你实际上只需要了解 TypeScript 的基础知识就可以开始生产应用了!
如果你已经在项目中使用 TypeScript,则可以通过NuGet获取它,也可以通过以下命令使用 npm 获取:
你还可以通过以下方式获得编辑器支持:
安装Visual Studio Code的内部版本,或按照以下说明使用新版本的TypeScript。
4.0 之旅
TypeScript 是当今许多人的 JavaScript 技术栈的核心部分。在 npm 上,TypeScript 在 7 月首次实现了超过 5000 万的月下载量!尽管我们知道它总有增长和改进的余地,但很明显,大多数使用 TypeScript 编码的开发人员确实很喜欢它。StackOverflow 的最新开发人员调查将 TypeScript 列为第二受欢迎的语言。在最新的 JS 现状调查中,使用 TypeScript 的开发人员中有大约 89%表示会再次使用它。
值得一提的是我们走到今天所走过的旅程。在之前的两个主要版本中,我们回顾了多年来闪耀的一些亮点。对于 TypeScript 4.0,我们将保持这种传统。
从 3.0 版本向前看,可以看到许多令人眼花缭乱的更改,但是 TypeScript 3.0 本身就产生了很大的冲击。统一元组类型和参数列表是当时的一大亮点,可在函数上启用大量已有的 JavaScript 模式。这个发行版还提供了项目参考,以帮助扩展、组织和共享代码库。3.0 版的一个产生重大影响的小更改是对 any 引入了类型安全的替代方法,称为 unknown。
TypeScript 3.1 扩展了映射类型的功能以处理元组和数组类型,并极大简化了将属性附加到函数的过程,而无需使用 TypeScript 专属的运行时功能(已停用)。
TypeScript 3.2 允许对象在泛型类型上传播,并通过严格类型化 bind、call 和 apply,利用 3.0 的功能更好地建模函数的元编程。TypeScript 3.3 更多关注稳定性,但也改进了联合类型方法,并在–build 模式下添加了文件增量式构建。
在 3.4 版本中,我们进一步支持函数式模式,更好地支持不可变数据结构,并改进了对高阶泛型函数的推断。这个发行版的一大改进是引入了–incremental 标志,该方法避免了在每次 TypeScript 运行时完全重建,从而加快了编译和类型检查的速度。
在 TypeScript 3.5 和 3.6 中加强了一些类型系统规则,还带来了更智能的兼容性检查规则。
TypeScript 3.7 是一个非常值得关注的版本,因为它很好地结合了许多新的类型系统特性与 ECMAScript 特性。在类型系统方面,我们加入了递归类型别名引用和对断言样式函数的支持,这两者都是独特的类型系统特性。从 JavaScript 方面来看,该版本带来了可选链和空值合并功能,这是 TypeScript 和 JavaScript 用户最期待的两项功能。
最近,3.8 和 3.9 带来了仅类型的导入/导出,以及许多 ECMAScript 特性,例如私有字段、模块中的顶级 await 和新的 export*语法。这些版本还带来了性能和可伸缩性优化。
我们还没有提到关于语言服务、基础架构、网站和其他核心项目中的那些工作,这些工作对于 TypeScript 的体验非常关键。核心团队的项目之外,我们在生态系统中还有非常出色的贡献者社区,他们推动了体验的不断改进,并通过 DefinitelyTyped 甚至 TypeScript 本身提供了帮助。在 2012 年刚开始时,DefinitelyTyped 仅有 80 个拉取请求。在 2019 年,它有超过 8300 个拉取请求,非常震撼人心。这些贡献是 TypeScript 体验的基础,这样一个繁忙而热情的社区在不断改善我们的生态系统,并推动我们不断改进,我们对此表示感谢。
新内容
可变元组类型
标记的元组元素
构造器的类属性推断
短路赋值运算符
catch子句绑定unknown
定制JSX工厂
加快了带有–noEmitOnError的build模式的速度
带有–noEmit的–incremental
编辑器改进
转换为可选链
/** @depreacted */
支持启动时的部分编辑模式
更智能的自动导入
我们的新网站!
重大更改
可变元组类型
考虑 JavaScript 中称为 concat 的函数,该函数接收两个数组或元组类型,并将它们连接在一起以创建一个新数组。
考虑 tail,它接收一个数组或元组,并返回除第一个元素外的所有元素。
我们如何在 TypeScript 中为它们类型化?
对于 concat,我们在较旧版本的 TS 中唯一可以做的就是尝试编写一些重载。
第二个数组始终为空时会冒出来七个重载。当 arr2 有一个参数时我们再加一些看看。
很明显这变得越来越离谱了。不幸的是,在类型化 tail 之类的函数时,你也会遇到同样的问题。
下面是另一种情况,我们称之为“被一千个重载搞垮”,它甚至什么问题都解决不了。它只为我们想写的重载提供正确的类型(不管重载有多少)。如果我们想做一个通行模式,就需要下面这种重载:
但在使用元组时,这个签名不会包含输入长度或元素顺序的任何信息。
TypeScript 4.0 带来了两个基础更改,并在推断方面进行了改进,从而可以类型化这些内容。
第一个更改是元组类型语法中的 spread 现在可以泛型。这意味着即使我们不知道要操作的实际类型,也可以表示对元组和数组的高阶操作。在这些元组类型中实例化泛型 spread(或用真实类型替换)时,它们可以产生其他数组和元组类型集。
例如,我们可以类型化 tail 那样的函数,而不会出现“被一千个重载搞垮”的问题。
第二个更改是,rest 元素可以出现在元组中的任何位置,而不仅仅是在结尾!
以前,TypeScript 会发出如下错误。
但是现在这种限制取消了。
当我们在没有已知长度的类型中 spread 时,结果类型也将变得不受限制,并且后面的所有元素都会变为结果的 rest 元素类型。
将这两种行为结合在一起,我们可以为 concat 编写一个类型良好的签名:
尽管一个签名仍然有些冗长,但它毕竟只有一个,只需写一次,并且在所有数组和元组上都具有可预测的行为。
这个功能很棒,在其他更复杂的场景中更有用。例如,考虑一个函数来部分应用参数,名为 partialCall。partialCall 接收一个函数(这里就叫 f)以及该函数期望的几个初始参数。然后,它返回一个新函数,接收它需要的其他所有参数,收到后调用 f。
TypeScript 4.0 改进了 rest 参数和 rest 元组元素的推断过程,因此我们可以类型化它并使其“正常工作”。
在这种情况下,partialCall 会知道其最初可以使用和不能使用哪些参数,并返回一个可以正确接收和拒绝剩余内容的函数。
可变元组类型创造了许多新模式,尤其是在函数组合方面。我们希望利用它来改善对 JavaScript 内置的 bind 方法的类型检查。此外还有其他一些推断改进和模式,想了解更多信息,可以查看可变元组的拉取请求。
标记的元组元素
改善元组类型和参数列表的体验很重要,因为它使我们能够围绕常见的 JavaScript 习惯用法进行强类型验证——实际上只是对参数列表进行切片和切块,并将它们传递给其他函数。对 rest 参数使用元组类型是其中的关键。
例如,以下函数使用元组类型作为 rest 参数:
……应该与以下函数没有区别……
……对于 foo 的任何调用者。
不过可读性就有区别了。在第一个示例中,我们没有第一个和第二个元素的参数名称。尽管这些对类型检查没有影响,但元组位置上缺少标记会难以传达我们的意图。
因此,在 TypeScript 4.0 中,元组类型现在可以提供标记。
为了进一步加强参数列表和元组类型之间的联系,我们让 rest 元素和可选元素的语法与参数列表的语法一致。
标记元组使用时有些规则,其中一条是:在标记一个元组元素时,还必须标记元组中的所有其他元素。
值得注意的是,在解构时标记不需要我们用不同的名称命名变量。它们纯粹是为文档和工具链服务的。
总的来说,当利用围绕元组和参数列表的模式,并以类型安全的方式实现重载时,带标记的元组非常方便好用。实际上,TypeScript 的编辑器支持会在可能的情况下将它们显示为重载。
了解更多信息,请查看带标记的元组元素的拉取请求。
构造器的类属性推断
当启用 noImplicitAny 时,TypeScript 4.0 现在可以使用控制流分析来确定类中属性的类型。
如果构造器的路径并非都分配给一个实例成员,则该属性可能被认为是 undefined 的。
如果你更了解某些情况(例如,你拥有某种 initialize 方法),则当你处于 strictPropertyInitialization 中时,需要使用显式类型注释以及明确的赋值断言(!)。
更多信息请见拉取请求。
短路赋值运算符
JavaScript 和许多语言都支持一组称为"复合赋值运算符"的运算符。复合赋值运算符将一个运算符应用于两个参数,然后将结果赋给左侧。你可能以前看过这些:
JavaScript 中有很多运算符都有对应的赋值运算符!但是有三个值得注意的例外:逻辑和(&&),逻辑或(||)和空值合并(??)。
所以 TypeScript 4.0 支持了一个新的 ECMAScript 特性,添加了三个新的赋值运算符:&&=,||=和??=。
这些运算符非常适合替换下面这种代码示例:
或者像下面这样的 if 代码段:
我们甚至看到了一些模式,可以在需要时懒惰地初始化值。
在极少数情况下,你使用带有副作用的 getter 或 setter 时,需要注意的是这些运算符仅在必要时执行赋值。从这个意义上讲,"短路"的不仅是运算符的右侧,赋值本身也短路了。
可以试着运行 这个示例,看看它和总是执行赋值有什么区别。
有关更多细节可以查看拉取请求。你也可以查看TC39的提案存储库。
catch 子句绑定支持 unknown
自 TypeScript 诞生以来,catch 子句变量始终按 any 类型化。这意味着 TypeScript 允许你对它们进行任何操作。
上述代码会有一些无法预期的行为!由于这些变量默认情况下的类型为 any,因此它们没有任何类型安全性可以防止无效操作。
因此,TypeScript 4.0 现在允许你将 catch 子句变量的类型指定为 unknown。unknown 比 any 更安全,因为它会在我们操作值之前提醒我们执行某种类型检查。
尽管默认情况下 catch 变量的类型不会更改,但我们将来可能会考虑使用新的–strict 模式标志,以便用户选择启用此行为。同时,应该可以编写一个 lint 规则来强制 catch 变量具有如下显式注解之一:: any 或: unknown。
有关更多信息,可以查看拉取请求。
定制 JSX 工厂
使用 JSX 时,fragment是 JSX 元素的一种,允许我们返回多个子元素。当我们第一次在 TypeScript 中实现 fragment 时,我们对其他库如何利用它们并不了解。如今,大多数鼓励使用 JSX 和支持 fragment 的库都具有类似的 API 设计。
在 TypeScript 4.0 中,用户可以通过新的 jsxFragmentFactory 选项来自定义 fragment 工厂。
例如,以下 tsconfig.json 文件告诉 TypeScript 以与 React 兼容的方式转换 JSX,但将每个工厂调用(invocation)切换为 h 而不是 React.createElement,并使用 Fragment 而不是 React.Fragment。
如果需要基于各个文件使用不同的 JSX 工厂,则可以利用新的/** @jsxFrag */注释。例如,下面的内容:
将输出成这样的 JavaScript:
查看拉取请求以获取更多信息!
加快了带有–noEmitOnError 的 build 模式的速度
以前,使用–noEmitOnError 标志时,当先前的编译在–incremental 下出现错误,编译速度将非常缓慢。这是因为基于–noEmitOnError 标志,上次编译的任何信息都不会缓存在.tsbuildinfo 文件中。
TypeScript 4.0 对此进行了更改,从而在这些情况下极大地提高了速度,进而改进了–build 模式的场景(这意味着同时有–incremental 和–noEmitOnError)。
有关详细信息,请查看拉取请求。
带有–noEmit 的–incremental
TypeScript 4.0 允许我们在利用–incremental 编译时使用–noEmit 标志。以前不允许这样做,因为–incremental 需要发出.tsbuildinfo 文件。
有关详细信息,请查看拉取请求。
编辑器改进
TypeScript 编译器不仅可以为大多数主流编辑器提供较好的 TS 编辑体验,还可以改进 Visual Studio 系列编辑器的 JavaScript 开发体验。
根据你使用的编辑器,在编辑器中使用新的 TypeScript/JavaScript 功能时会有区别:
Visual Studio Code支持选择不同版本的TypeScript。另外,还有JavaScript/TypeScript Nightly Extension来紧跟最新版本(通常非常稳定)。
更多信息见 TS 编辑器支持列表。
转换为可选链
可选链是一项新功能,受到了广泛的欢迎。TypeScript 4.0 在转换常见模式时可以利用可选链和空值合并的优势!
我们认为这种重构应该能捕获大多数用例的意图,尤其是当 TypeScript 对你的类型有更精确的了解时。
有关详细信息,请查看拉取请求。
/** @deprecated */
支持
现在,TypeScript 的编辑支持可以识别声明中是否带有/** @deprecated*/
JSDoc 注释。该信息显示在自动完成列表中,并作为编辑器可以特别处理的建议诊断。在像 VSCode 这样的编辑器中,deprecated 的值通常显示为删除线样式。
有关详细信息,查看拉取请求。
启动时的部分编辑模式
很多用户抱怨启动时间缓慢,尤其是在大型项目中。具体来说,罪魁祸首通常是一个称为项目加载的过程,该过程与我们编译器的程序构建步骤大致相同。这一过程从一组初始文件开始,解析它们、解析它们的依赖、再解析那些依赖,解析那些依赖的依赖,等等,最后需要花费很长时间。项目越大,启动延迟可能会越长。
所以我们一直在努力为开发人员提供一种新的模式,在获得完整的语言服务体验之前提供部分体验。这里的核心思想是,编辑者可以运行仅具有单个文件视图的轻量级部分服务器。
这种新模式可以将 TypeScript 在代码库上开始交互之前的准备时间从 20 秒到 1 分钟缩短到只有几秒钟。比如说,在较大的代码库上重启编辑器时,TS 3.9 版没法立即提供自动完成和快速信息;另一方面,TS 4.0 可以立即提供完整的编辑体验,同时在后台加载整个项目。
当前,唯一支持此模式的编辑器是 Visual Studio Code,但 UX 和功能仍有改进的余地。我们列出了准备加入的改进,希望获得更多反馈。
有关更多信息,你可以查看原始提案,拉取请求,以及后续的meta问题。
更智能的自动导入
自动导入是一个了不起的功能。但是,自动导入在用 TypeScript 编写的包上不起作用——也就是说,我们得在项目的其他位置至少写了一个显式导入。
为什么自动导入适用于 @types 软件包,而不适用于使用自己类型的包呢?其实自动导入是通过检查项目中已经包含的软件包来实现的。TypeScript 有一个怪癖,可以自动包括 node_modules/@types 中的所有包,而忽略其他包;但爬取所有 node_modules 包的开销可能会很昂贵。
当你尝试自动导入刚刚安装但尚未使用的内容时,这些都会导致糟糕的体验。
TypeScript 4.0 现在可以包含你在 package.json 的 dependencies(和 peerDependencies)字段中列出的包。这些包中的信息仅用于改进自动导入,不会更改类型检查等其他内容。这样就避免了遍历 node_modules 目录的成本,使我们可以为所有带类型的依赖项提供自动导入。
当你的 package.json 列出了超过十项尚未导入的类型化依赖项时,这个功能会自动禁用,以避免缓慢的项目加载过程。要强制开启它或完全禁用它,你可以配置编辑器。在 Visual Studio Code 中是"Include Package JSON Auto Imports"设置(或 typescript.preferences.includePackageJsonAutoImports)。
我们的新网站!
TypeScript 网站最近被彻底重写了!
详细信息可以参考之前的文章:
重大更改
lib.d.ts
我们的 lib.d.ts 声明已更改,具体来说是 DOM 的类型已更改。主要是删除了 document.origin,它仅在 IE 的旧版本中有效,而 Safari MDN 建议改用 self.origin。
属性重写访问器(反之亦然)是错误
以前,只有在使用 useDefineForClassFields 时,属性重写访问器或访问器重写属性是一个错误;但现在,在派生类中声明一个将重写基类中的 getter 或 setter 的属性时总是发出错误。
有关详细信息,查看拉取请求。
delete 的操作数必须是可选的。
在 strictNullChecks 中使用 delete 运算符时,操作数现在必须为 any、unknown、never 或为可选(因为它在类型中包含 undefined)。否则,使用 delete 运算符是错误的。
关于更多信息,查看拉取请求。
TypeScript 的 Node 工厂用法已弃用
如今,TypeScript 提供了一组用于生成 AST 节点的“工厂”函数。但是,TypeScript 4.0 提供了新的 node 工厂 API。因此 TypeScript 4.0 决定弃用使用这些旧函数,推荐改用新函数。
有关更多信息,请查看拉取请求。
下一步计划
TypeScript 4.1 的迭代计划已经上线了,你可以大致了解一下。同时,你可以在工作区或编辑器中使用 nightly 构建来预览 4.1 中添加的新特性。无论你是在使用 TypeScript 4.0 还是下一版本,我们都希望听到你的反馈!可以通过Twitter联系我们,或在 GitHub 上发起问题。
我们再一次为社区所做的一切工作和奉献精神深表感谢。我们希望让 TypeScript 和 JavaScript 的编码体验成为你应得的纯粹乐趣。为此,我们需要改善语言和编辑体验、提升性能、迭代我们的用户体验、降低入门和学习的门槛等等。
非常感谢大家,请享用 4.0 版本吧,编程愉快!
原文链接:
https://devblogs.microsoft.com/typescript/announcing-typescript-4-0/
评论