欢迎光临散文网 会员登陆 & 注册

TypeScript 5.0 已发布!看看增加了什么新功能

2023-03-21 15:08 作者:支付宝体验科技  | 我要投稿

本文作者是蚂蚁集团前端工程师厚发,基于《TypeScript 5.0 Beta 发布》,结合最新的发布说明内容,重新修改的。TypeScript 正式迎来 5.0 时代。

自 Beta 版本以来,有几个显著的更改

其中一个新变化是 TypeScript 允许在 export 和 export default 之前或之后放置装饰器。这一变化反映了 TC39(ECMAScript/JavaScript 的标准组织)内的讨论和共识。

另一个变化是,新的模块解析选项(moduleResolution)“bundler” 现在只能在将 --module 选项设置为 esnext 时使用。这是为了确保在 bundler 解析 import 语句之前,不管 bundler 或加载器(loader)是否使用 TypeScript 的模块选项,输入文件中编写的 import 语句都不会转换为 require 调用。在这些发行说明中,我们也提供了一些上下文信息,建议大多数库作者使用 node16 或 nodenext

尽管 TypeScript 5.0 Beta 版本中已经具备了此功能,但我们没有为支持编辑器场景中不区分大小写的导入排序编写文档。这在一定程度上是因为自定义 UX 仍在讨论中,但是默认情况下,TypeScript 现在应该与您的其他工具更好地配合使用。具体介绍在后面。

自我们发布 RC 版本以来,最显着的变化是 TypeScript 5.0 现在在 package.json 中指定了 Node.js 的最低版本为 12.20。我们还发布了一篇有关 TypeScript 5.0 迁移到模块的文章,并链接到了它。(https://devblogs.microsoft.com/typescript/typescripts-migration-to-modules/)

自 TypeScript 5.0 Beta 和 RC 发布以来,速度基准测试和包大小差异的具体数字也已经进行了调整,尽管噪声是运行的一个因素。一些基准测试的名称也已经进行了调整以提高清晰度,并且包大小的改进已经移动到一个单独的图表中。

BreakChanges And Deprecations

运行时要求

要求 Node.js 10.x 以上。

lib.d.ts变更

例行环节。具体变更在这:lib.d.ts change (https://github.com/microsoft/TypeScript/pull/52328/files)

后面实际项目中如果有遇到一些常用的用法报错了,需要手动更改代码的 case,笔者会在这里补充,目前还没发现。

关系运算符中禁止隐式类型转换

5.0 之前 TypeScript 只会检查 + - * /运算符的隐式类型转换,提示类型报错。

5.0 之后关系运算符 > < <= >=也检查。

经典操作,可以通过 + 运算符来进行进行转换。


enum 类型检修

自从 TypeScript 支持 enum 枚举类型以来,一直都有些长期存在的奇怪的问题。官方说在 5.0,他们正在处理解决这些问题。

给 enum 类型变量赋值字面量时,如果超出了 enum 定义范围,会报错

注意是字面量。下面这种情况还是不会报错:


修复之前的一个问题:由多个 enum 类型组成的混合 enum 类型,enum 的成员都会是 number 类型

官方的例子:


修饰器

ECMASCript 的修饰器标准已经进到了 Stage3 阶段了,TypeScript 5.0 落地了 ECMASCript 的修饰器标准。修饰器简单来说是一个函数,可以用于修饰类、类的成员方法/属性。

修饰器使用说明

官方以一个类方法修饰器来举了个例子:

代码中,loggedMethod就是一个类方法修饰器。它是一个函数,函数的第一个入参是被修饰的greeet方法的初始值,第二个入参context是上下文信息。修饰器最后返回的是replacementMethod函数,当被修饰的方法greet被调用时,执行的就是replacementMethod函数。我们可以把一些可复用的代码逻辑放到loggedMethod修饰器中,那就可以基于修饰器去复用代码了。

修饰器上下文

非常值得关注的是,第二个入参context提供了一个上下文信息对象。根据修饰器的种类不同有不同的类型:类修饰器:ClassDecoratorContext、类方法修饰器:ClassMethodDecoratorContext、类属性getter修饰器:ClassGetterDecoratorContext、类属性setter修饰器:ClassSetterDecoratorContext、类属性修饰器:ClassFieldDecoratorContext、类accessor修饰器:ClassAccessorDecoratorContext

context的类型定义大致是这样的:


addInitializer(新语法)

上下文中的addInitializer方法是标准中的新语法。官方文档解释:

It’s a way to hook into the beginning of the constructor (or the initialization of the class itself if we’re working with statics)

当修饰器用来修饰非 static 属性/方法时,可以通过这个方法在实例初始化时,构造函数执行之前指定执行逻辑。当修饰器用来修饰 static 属性/方法时,可以通过这个方法类初始化时,指定执行逻辑。举个例子就清晰很多:


Initializer 方法执行时机

类的静态属性/方法初始化,在类初始化过程中。

staticMethodDecoratorstaticFiledDecorator中通过addInitializer方法增加的初始化函数,先执行。

classFiledDecorator中通过addInitializer方法增加的初始化函数,在类初始化之后执行。类进行实例化                               

instanceMethodinstanceFiledDecorator中通过addInitializer方法增加的初始化函数,在实例初始化时,构造函数执行之前执行。

Initializer 方法的应用

官网例子:使用addInitializer()绑定this


与之前版本实验性的修饰器的不同

之前版本的 TypeScript 也支持修饰器(https://typescript.bootcss.com/decorators.html),需要增加--experimentalDecorators编译选项。

TypeScript 5.0 的修饰器标准跟之前的修饰器是不兼容的。旧版的 --experimentalDecorators 选项将会仍然保留,如果启用此配置,则仍然会将装饰器视为旧版,新版的装饰器无需任何配置就能够默认启用。

TypeScript5.0 的修饰器标准跟之前的元数据反射(https://www.typescriptlang.org/docs/handbook/decorators.html#metadata)是不兼容的。

写类型完备的修饰器

推荐写类型完备的修饰器。如果写类型完备的修饰器,不免会用到很多泛型、类型参数,这样也会影响代码的可读性。怎么写修饰器后面会有更多的文档出来。先推荐了一篇文章:《JavaScript metaprogramming with the 2022-03 decorators API》https://2ality.com/2022/10/javascript-decorators.html

const Type Parameters

提供const修饰符,对泛型的类型参数进行修饰。用于解决之前 需要 增加as const断言才能实现的类型推导。

5.0 之前对于泛型的类型参数的类型推导,TypeScript 往往只能推导到基础数据类型,这样做是保证变量类型的可变。例子:

但是,从getConstValue函数真实的意图--得到一个不可变的常量,来说这样的类型推导是不完备的。之前我们往往这么做:

在5.0版本中,可以不需要这样的骚操作了,把const修饰符加上作用于泛型的类型参数即可。优雅!


与泛型约束一起使用

注意当const修饰符修饰的类型变量,后面带有泛型约束extends时,如泛型约束不是常量,那么类型推导的结果遵循泛型约束,而不是常量。

官网的例子:


作用范围

const修饰符的类型推导生效范围:函数调用时,参数是对象、数组或表达式。如果函数调用时,参数是一个变量,上面讲述的类型推导不会生效。

官网例子:


compilerOptions 的 extends 配置支持多文件


枚举

TypeScript 5.0 之前的枚举,枚举分为数字枚举和字符串枚举。

TypeScript 5.0 将所有枚举合并为统一的一种枚举类型(Union enums),其含义就是枚举类型是其所有枚举成员类型组成的联合类型。

这样做带来什么改变呢?下面列举了一些 5.0 之前,在使用枚举时,会碰到的一些很神奇诡异的规则,然后跟 5.0 之后做一下对比,看有什么改变。

前后对比

  • 5.0 之前:把枚举成员用作类型,所有枚举成员必须使用字面量初始化。

  • 5.0 之后:没有这个约束了。

github 相关的#27976(https://github.com/microsoft/TypeScript/issues/27976)

UserResponse枚举中,存在UserResponse.Yes``UserResponse.NotSure两个用非字面量初始化的成员,那么使用成员去当TypeScript中的类型使用时,就会报错:

Enum type 'UserResponse' has members with initializers that are not literals.(2535) 

必须把枚举成员全部使用字面量来初始化,可以对比着UserResponse_1来看。

  • 5.0 之前:字符串枚举成员只能是常量枚举成员。比如,无法使用字符串变量或者模版字符串给枚举成员赋值。

  • 5.0 之后:字符串枚举成员可以是计算枚举成员。


  • 5.0 之前:枚举成员的初始化,存在一些约束。

  • 5.0 之后:约束的第二条由「数字字面量」放宽到「数字字面量和数字常量」。其他的计算枚举成员还是会报错。

(感觉规则又变复杂了。不过用起来会方便一点。)


疑问

疑问一

那问题来了,5.0 版本之后,类似E_1.aE_1.c这样(使用数字常量初始化或带有数字常量表达式)的枚举成员,是「计算枚举成员」呢还是「常量枚举成员」。

  • 按照「计算枚举成员」和「常量枚举成员」的定义(https://www.typescriptlang.org/docs/handbook/enums.html#computed-and-constant-members)
    ,它是「计算枚举成员」

  • 按照枚举成员的初始化约束,它又是「常量枚举成员」

感觉「计算枚举成员」和「常量枚举成员」的定义(https://www.typescriptlang.org/docs/handbook/enums.html#computed-and-constant-members)要修改,到目前官方文档还没修改。

moduleResolution 配置新增 bundler支持

TypeScript 4.7  (https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#esm-nodejs)为 --module 和 --moduleResolution增加了node16nodenext,从而更好地在 Nodejs 中支持 ESM 标准。但是在这种模式下,会有很多限制。

比如,在 Nodejs 中,ESM(ECMAScript module)要求在import相对路径的依赖时,需要显示写文件的扩展名。

这样做的原因是为了在文件服务器中有更好的文件搜寻速度。对比使用其他构建工具,node16/nodenext模式下的这些限制还是太麻烦了,甚至说默认的node模式更好。

但是,默认的node模式已经过时了,大多数现代构建工具混合着使用ESM(ECMAScript module)和CommonJS两种模块标准的模块解析策略。

于是,5.0 中 TypeScript 提供了新的moduleResolution配置选项bundler,它同时兼容 ESM(ECMAScript module) 和 CommonJS 两种模块标准的模块解析策略,但是又没有 ESM 在 Nodejs 中的限制。

跟 moduleResolution 相关的几个配置项

allowImportingTsExtensions

配置启用后,import其他模块时允许携带.ts.mts和 .tsx这三种扩展名。这个配置启用必须同时与--noEmit --emitDeclarationOnly这两个配置一起启用。

--moduleResolutionnode16nodenextbundler是,配置默认启用。

resolvePackageJsonExports与resolvePackageJsonImports

配置启用后,import来自node_modules中的模块时,TypeScript 会去解析模块对应的package.json中的exportsimports字段。这块可以去看一下 Conditional exports (https://nodejs.org/api/packages.html#conditional-exports)的知识。当--moduleResolutionnode16nodenextbundler是,配置默认启用。

allowArbitraryExtensions

允许任意的后缀名。当在代码里 import 的模块扩展名不是.js``.jsx``.ts``.tsx,编译器会按以下的规则去查找该模块的类型定义文件:{file basename}.d.{extension}.ts

官网例子:

默认的话,TypeScript 会提示一个错误,让你知道 TypeScript 无法解析这种文件类型,代码运行时可能无法正确地导入。但是如果你在 bundler 中正确地配置,可以加上--allowArbitraryExtensions这个新的编译选项来阻止错误的提示。

在前端编写 CSS Modules 的时候,以前是这样处理 import css/less 的。

现在通过这个编译选项可以增加上类型支持了。但是,针对 css/less 等样式文件,我可能还是会这样处理,感觉加上类型没太大必要。

--moduleResolutionnode16nodenextbundler是,配置默认启用。

customConditions

Conditional exports中还支持自定义conditions (https://nodejs.org/api/packages.html#resolving-user-conditions)。通过这个配置进行支持。

当我们需要import一个模块es-module-package,它的package.json是下面这样定义的话    

那么tsconfig.json可以这么写

--moduleResolutionnode16nodenextbundler是,配置默认启用。

verbatimModuleSyntax

提供了一个新的配置项,用于简化之前 TypeScript 的引用省略功能涉及importsNotUsedAsValuespreserveValueImportsisolatedModules三个配置项的配置复杂问题。

支持 export type *

支持使用export * from "module"export * as ns from "module"这样的语句进行类型导出(https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export)。

JSDoc 支持 @satisfies

TypeScript 4.9 的时候支持了[satisfies](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html)操作符。现在在 JSDoc 上也支持@satisfies注释,因为有部分开发者是通过 JSDoc 注释来给 Javascript 提供类型检查的。

JSDoc 支持 @overload

TypeScript 中你可以定义同一个函数的不同的入参类型或者不同的返回值类型。现在在 JSDoc 上支持通过@overload注释来满足这个需求。

tsc build 模式下支持设置以下几个标志位

--declaration--emitDeclarationOnly--declarationMap--soureMap--inlineSourceMap

编辑器中的不区分大小写的导入排序

在类似于 Visual Studio 和 VS Code 这样的编辑器中,TypeScript 为组织和排序导入和导出提供支持。不过,对于何时排序的列表会有不同的解释。

例如,以下导入列表是否已排序?

令人惊讶的是,答案可能是“取决于情况”。如果我们不关心大小写,那么这个列表显然没有排序。字母 "f" 在 "t" 和 "T" 之前。

但在大多数编程语言中,排序默认比较字符串的字节值。JavaScript 比较字符串的方式意味着 "Toggle" 总是排在 "freeze" 之前,因为根据 ASCII 字符编码 (https://en.wikipedia.org/wiki/ASCII),大写字母在小写字母之前。因此,从这个角度来看,导入列表已排序。

TypeScript 以前认为导入列表已排序,因为它进行了基本的区分大小写排序。这可能是开发人员的痛点,因为他们更喜欢不区分大小写的排序方式,或者使用像 ESLint 这样的工具默认需要不区分大小写的排序方式。

TypeScript 现在默认检测大小写敏感性。这意味着 TypeScript 和类似 ESLint 的工具通常不会因为如何最好地排序导入而“争执”。

我们的团队还在尝试更多的排序策略,您可以在此处阅读更多信息(https://github.com/microsoft/TypeScript/pull/52115)。这些选项可能最终可以由编辑器进行配置。目前,它们仍然是不稳定和实验性的,您可以通过在 VS Code 中使用 JSON 选项中的 typeScript.unstable 条目来选择它们。以下是您可以尝试的所有选项(默认设置):


完善的 switch/case 代码补全


  速度、内存和包体积的优化

官方称 5.0 版本,不管是代码结构、数据结构还是算法实现都进行了很“强大”的改变。在使用 TypeScript 的时候,感觉会变得更快,不仅仅是在运行 TypeScript 的时候,甚至在安装它的时候都会觉得更快。

举了两个例子:

  • 使用 TypeScript 5.0 Beta 构建 VS Code 花的时间只占了使用 4.9 去构建的 81%。

  • TypeScript 5.0 Beta(37.3MB) 包体积是 4.9(63.8MB)的 58%。

TypeScript 5.0 更新了一些值得注意的改进,下面我们会逐一介绍。

首先,我们最近将 TypeScript 从命名空间转移到了模块中,这使我们能够利用现代构建工具来执行优化,如作用域提升。使用这些工具,重新审视我们的打包策略,并删除一些废弃的代码,使 TypeScript 4.9 的63.8 MB 包大小减小了约 26.4 MB。这也通过直接函数调用带来了显着的加速。我们在这里写了一篇关于我们迁移到模块的详细说明(https://devblogs.microsoft.com/typescript/typescripts-migration-to-modules/)。

TypeScript 还增加了对编译器内部对象类型的更一致性,并在一些对象类型上减少了存储的数据。这减少了多态操作,同时平衡了由于使我们的对象结构更统一而带来的内存使用增加。

我们还对序列化信息到字符串时进行了一些缓存。类型显示,可能会作为错误报告、声明发出、代码完成等的一部分发生,可能会相当昂贵。TypeScript现在对一些常用的机制进行了缓存,以便在这些操作之间重复使用。

我们所做的另一个值得注意的改变是,利用var关键字偶尔规避在闭包中使用letconst所产生的成本,这提高了我们的一些解析性能。

总的来说,我们期望大多数代码库应该会从 TypeScript 5.0 中看到速度的提升,并且一直能够重复获得 10% 到 20% 的胜利。当然,这将取决于硬件和代码库的特征,但我们鼓励您今天就在您的代码库上尝试它!

有关更多信息,请参见我们的一些值得注意的优化:

  • 迁移到模块 https://github.com/microsoft/TypeScript/pull/51387

  • 节点单态化 https://github.com/microsoft/TypeScript/pull/51682

  • Symbol 单态化 https://github.com/microsoft/TypeScript/pull/51880

  • 标识符大小减小 https://github.com/microsoft/TypeScript/pull/52170

  • Printer 缓存 https://github.com/microsoft/TypeScript/pull/52382

  • 有限使用 var https://github.com/microsoft/TypeScript/issues/52924

  参考

《Announcing TypeScript 5.0》

https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/

《Announcing TypeScript 5.0 Beta》

https://devblogs.microsoft.com/typescript/announcing-typescript-5-0-beta/


TypeScript 5.0 已发布!看看增加了什么新功能的评论 (共 条)

分享到微博请遵守国家法律