本文作者是蚂蚁集团前端工程师厚发,基于《TypeScript 5.0 Beta 发布》,结合最新的发布说明内容,重新修改的。TypeScript 正式迎来 5.0 时代。
特别说明:文中浅灰色背景的内容,均由 ChatGPT 进行翻译,绿色字体的内容是为了保证上下文顺畅,作者人工纠正的内容,再人工附加上外链。最终版可见绿色字体的内容是很少的,ChatGPT 👍 👍
自 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 只会检查 +
-
*
/
运算符的隐式类型转换,提示类型报错。
function func(ns: number | string) { return ns * 4; // Error, possible implicit coercion }
5.0 之后关系运算符 >
<
<=
>=
也检查。
function func(ns: number | string) { return ns > 4; // Now also an error }
经典操作,可以通过 +
运算符来进行进行转换。
function func(ns: number | string) { return +ns > 4; // OK }
enum 类型检修
自从 TypeScript 支持 enum 枚举类型以来,一直都有些长期存在的奇怪的问题。官方说在 5.0,他们正在处理解决这些问题。
给 enum 类型变量赋值字面量时,如果超出了 enum 定义范围,会报错
enum SomeEvenDigit { Zero = 0, Two = 2, Four = 4 } // Now correctly an error let m: SomeEvenDigit = 1;
注意是字面量。下面这种情况还是不会报错:
enum SomeEvenDigit { Zero = 0, Two = 2, Four = 4 } const test = { number: 2, }; let m: SomeEvenDigit = test.number;
修复之前的一个问题:由多个 enum 类型组成的混合 enum 类型,enum 的成员都会是 number 类型
官方的例子:
enum Letters { A = "a" } enum Numbers { one = 1, two = Letters.A } // 5.0 之前,这个语句是不会报错的,因为enum的成员都被错误地认为是number类型 // 5.0 之后,这个语句会报类型错误, // Type 'Numbers' is not assignable to type 'number'.ts(2322) const t: number = Numbers.two;
修饰器
ECMASCript 的修饰器标准已经进到了 Stage3 阶段了,TypeScript 5.0 落地了 ECMASCript 的修饰器标准。修饰器简单来说是一个函数,可以用于修饰类、类的成员方法/属性。
修饰器使用说明
官方以一个类方法修饰器来举了个例子:
function loggedMethod(originalMethod: any, context: ClassMethodDecoratorContext) { const methodName = String(context.name); function replacementMethod(this: any, ...args: any[]) { console.log(`LOG: Entering method '${methodName}'.`) const result = originalMethod.call(this, ...args); console.log(`LOG: Exiting method '${methodName}'.`) return result; } return replacementMethod; } class Person { name: string; constructor(name: string) { this.name = name; } @loggedMethod greet() { console.log(`Hello, my name is ${this.name}.`); } } const p = new Person("Ray"); p.greet();
代码中,loggedMethod
就是一个类方法修饰器。它是一个函数,函数的第一个入参是被修饰的greeet
方法的初始值,第二个入参context
是上下文信息。修饰器最后返回的是replacementMethod
函数,当被修饰的方法greet
被调用时,执行的就是replacementMethod
函数。我们可以把一些可复用的代码逻辑放到loggedMethod
修饰器中,那就可以基于修饰器去复用代码了。
修饰器上下文
非常值得关注的是,第二个入参context
提供了一个上下文信息对象。根据修饰器的种类不同有不同的类型:类修饰器:ClassDecoratorContext
、类方法修饰器:ClassMethodDecoratorContext
、类属性getter修饰器:ClassGetterDecoratorContext
、类属性setter修饰器:ClassSetterDecoratorContext
、类属性修饰器:ClassFieldDecoratorContext
、类accessor修饰器:ClassAccessorDecoratorContext
context
的类型定义大致是这样的:
{ // 不同的类型有不同的值。 // ClassDecoratorContext ==> class // ClassMethodDecoratorContext ==> method // ClassGetterDecoratorContext ==> getter // ClassSetterDecoratorContext ==> setter // ClassFieldDecoratorContext ==> accessor // ClassFieldDecoratorContext ==> field readonly kind: string; // 被修饰的类的名称 或者 类成员的名称 readonly name: string; // 用于添加 类的构造函数执行前 的逻辑 addInitializer(initializer: () => void): void }
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 属性/方法时,可以通过这个方法类初始化时,指定执行逻辑。举个例子就清晰很多:
function classDecorator(target: any, context: ClassDecoratorContext) { context.addInitializer(() => { console.log('classDecorator addInitializer here', target); }) } function staticFiledDecorator(target: any, context: ClassFieldDecoratorContext) { context.addInitializer(() => { console.log('staticFiledDecorator addInitializer here', target); }) } function staticMethodDecorator(target: any, context: ClassMethodDecoratorContext) { context.addInitializer(() => { console.log('staticMethodDecorator addInitializer here', target); }) } function instanceFiledDecorator(target: any, context: ClassFieldDecoratorContext) { context.addInitializer(() => { console.log('instanceFiledDecorator addInitializer here', target); }) } function instanceMethodDecorator(target: any, context: ClassMethodDecoratorContext) { context.addInitializer(() => { console.log('instanceMethod addInitializer here', target); }) function replacementMethod(this: any, ...args: any[]) { const result = target.call(this, ...args); return result; } return replacementMethod; } @classDecorator class Person { @staticFiledDecorator static age: number = 23; @staticMethodDecorator static run() { console.log('run'); } constructor(name: string) { console.log('constructor'); this.name = name; } @instanceFiledDecorator name: string = 'Forest'; @instanceMethodDecorator eat() { console.log('eat sth'); } } const p = new Person("Ray"); // 最终的输出 // staticMethodDecorator addInitializer here [Function: run] // staticFiledDecorator addInitializer here undefined // classDecorator addInitializer here [class Person] { age: 23 } // instanceMethod addInitializer here [Function: eat] // instanceFiledDecorator addInitializer here undefined // constructor
Initializer
方法执行时机
类的静态属性/方法初始化,在类初始化过程中。staticMethodDecorator
、staticFiledDecorator
中通过addInitializer
方法增加的初始化函数,先执行。classFiledDecorator
中通过addInitializer
方法增加的初始化函数,在类初始化之后执行。类进行实例化
const p = new Person("Ray");
instanceMethod
、instanceFiledDecorator
中通过addInitializer
方法增加的初始化函数,在实例初始化时,构造函数执行之前执行。
Initializer
方法的应用
官网例子:使用addInitializer()
绑定this
。
function bound(originalMethod: any, context: ClassMethodDecoratorContext) { const methodName = context.name; if (context.private) { throw new Error(`'bound' cannot decorate private properties like ${methodName as string}.`); } context.addInitializer(function () { this[methodName] = this[methodName].bind(this); }); } class Person { name: string; constructor(name: string) { this.name = name; } @bound @loggedMethod greet() { console.log(`Hello, my name is ${this.name}.`); } } const p = new Person("Ray"); const greet = p.greet; // Works! greet();
与之前版本实验性的修饰器的不同
之前版本的 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 往往只能推导到基础数据类型,这样做是保证变量类型的可变。例子:
function getConstValue<T>(arg: T): T { return arg; } // 这里推导出 names 是 string[] const names = getConstValue(["Jack", "Bob", "Eve"]); // 这里推导出 result 是 { name: string, frd: string} const result = getConstValue({ name: 'Jack', frd: ['Bob', 'Eve'] }); // 不会报类型错误 names[0] = '1'; // 不会报类型错误 result.frd[0] = 'Sophia';
但是,从getConstValue
函数真实的意图--得到一个不可变的常量,来说这样的类型推导是不完备的。之前我们往往这么做:
function getConstValue<T>(arg: T): T { return arg; } // 这里推导出 names 是 readonly ["Alice", "Bob", "Eve"] const names = getConstValue(["Jack", "Bob", "Eve"] as const); // 这里推导出 result 是 { // readonly name: "linbudu"; // readonly techs: readonly ["nodejs", "typescript", "graphql"]; // } const result = getConstValue({ name: 'Jack', frd: ['Bob', 'Eve'] } as const); // 报类型错误 names[0] = '1'; // 报类型错误 result.frd[0] = 'Sophia';
在5.0版本中,可以不需要这样的骚操作了,把const
修饰符加上作用于泛型的类型参数即可。优雅!
function getConstValue<const T>(arg: T): T { return arg; } // 这里推导出 names 是 readonly ["Alice", "Bob", "Eve"] const names = getConstValue(["Jack", "Bob", "Eve"]); // 这里推导出 result 是 { // readonly name: "linbudu"; // readonly techs: readonly ["nodejs", "typescript", "graphql"]; // } const result = getConstValue({ name: 'Jack', frd: ['Bob', 'Eve'] }); // 报类型错误 names[0] = '1'; // 报类型错误 result.frd[0] = 'Sophia';
与泛型约束一起使用
注意当const
修饰符修饰的类型变量,后面带有泛型约束extends
时,如泛型约束不是常量,那么类型推导的结果遵循泛型约束,而不是常量。
官网的例子:
declare function fnBad<const T extends string[]>(args: T): void; // 此时推断出来T的类型是 string[],而不是 readonly ["a", "b", "c"] fnBad(["a", "b" ,"c"]); // 泛型约束 后面跟着的是 readonly string[] declare function fnGood<const T extends readonly string[]>(args: T): void; // 此时推断出来T的类型是 ["a", "b", "c"] fnGood(["a", "b" ,"c"]);
作用范围
const
修饰符的类型推导生效范围:函数调用时,参数是对象、数组或表达式。如果函数调用时,参数是一个变量,上面讲述的类型推导不会生效。
官网例子:
declare function fnGood<const T extends readonly string[]>(args: T): void; const arr = ["a", "b" ,"c"]; // 'T' is still 'string[]'-- the 'const' modifier has no effect here fnGood(arr);
compilerOptions 的 extends 配置支持多文件
{ "compilerOptions": { "extends": ["./tsconfig1.json", "./tsconfig2.json"] } }
枚举
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)
// 5.0之前 // it‘s ok enum UserResponse { // constant member No = 0, // computed member Yes = num, // constant member NotSure = 1 + 1 } // throws an error : Enum type has members with initializers that are not literals type aType = UserResponse.Yes; // ^^^^^^^^^^^^^^^^^ // throws an error : Enum type has members with initializers that are not literals type bType = UserResponse.NotSure; // ^^^^^^^^^^^^^^^^^^^^^ // throws an error : Enum type has members with initializers that are not literals type cType = UserResponse.No; // ^^^^^^^^^^^^^^^^ // throws an error : Enum type has members with initializers that are not literals // it‘s ok enum UserResponse_1 { // constant member initialized by literal No = 0, // constant member initialized by literal Yes = 1, // constant member initialized by literal NotSure = 2, } // it's ok type aType_1 = UserResponse_1.Yes; type bType_1 = UserResponse_1.NotSure;
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之前 const string_var = 'jack'; enum String_E { // it‘s ok a = 'a' } enum String_E2 { aa = string_var, // ^^^^^^^^^^ // throws an error :Only numeric enums can have computed members bb = `${String_E.a}` // ^^^^^^^^^^^^^ // throws an error :Only numeric enums can have computed members }
- 5.0 之前:枚举成员的初始化,存在一些约束。
- 5.0 之后:约束的第二条由「数字字面量」放宽到「数字字面量和数字常量」。其他的计算枚举成员还是会报错。
(感觉规则又变复杂了。不过用起来会方便一点。)
// 5.0之前 const num = 50; enum E { a = num, b, // ^ // throws an error : Enum member must have initializer. } // 5.0之后 enum E { a = num, // 不报错,b的值为 51 b, } function getNumber() { return 50; } enum E_1 { a = num + 1, // 不报错,b的值为 52 b, c = num << 1, // 不报错,d的值为 52 d, e = getNumber(), f, //^ // throws an error : Enum member must have initializer }
疑问
疑问一
const num = 50; enum E_1 { a = num + 1, // 不报错,b的值为 52 b, c = num << 1, // 不报错,d的值为 52 d, e = getNumber(), f, //^ // throws an error : Enum member must have initializer }
那问题来了,5.0 版本之后,类似E_1.a
、E_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
增加了node16
和nodenext
,从而更好地在 Nodejs 中支持 ESM 标准。但是在这种模式下,会有很多限制。
比如,在 Nodejs 中,ESM(ECMAScript module)要求在import
相对路径的依赖时,需要显示写文件的扩展名。
// entry.mjs import * as utils from "./utils"; // wrong - we need to include the file extension. import * as utils from "./utils.mjs"; // works
这样做的原因是为了在文件服务器中有更好的文件搜寻速度。对比使用其他构建工具,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
这两个配置一起启用。
当--moduleResolution
是node16
、nodenext
、bundler
是,配置默认启用。
resolvePackageJsonExports与resolvePackageJsonImports
配置启用后,import
来自node_modules
中的模块时,TypeScript 会去解析模块对应的package.json
中的exports
和imports
字段。这块可以去看一下 Conditional exports (https://nodejs.org/api/packages.html#conditional-exports)的知识。当--moduleResolution
是node16
、nodenext
、bundler
是,配置默认启用。
allowArbitraryExtensions
允许任意的后缀名。当在代码里 import 的模块扩展名不是.js``.jsx``.ts``.tsx
,编译器会按以下的规则去查找该模块的类型定义文件:{file basename}.d.{extension}.ts
。
官网例子:
/* app.css */ .cookie-banner { display: none; }
// app.d.css.ts declare const css: { cookieBanner: string; }; export default css;
// App.tsx import styles from "./app.css"; styles.cookieBanner; // string
默认的话,TypeScript 会提示一个错误,让你知道 TypeScript 无法解析这种文件类型,代码运行时可能无法正确地导入。但是如果你在 bundler 中正确地配置,可以加上--allowArbitraryExtensions
这个新的编译选项来阻止错误的提示。
在前端编写 CSS Modules 的时候,以前是这样处理 import css/less 的。
declare module '*.less'; declare module '*.css';
现在通过这个编译选项可以增加上类型支持了。但是,针对 css/less 等样式文件,我可能还是会这样处理,感觉加上类型没太大必要。
当--moduleResolution
是node16
、nodenext
、bundler
是,配置默认启用。
customConditions
Conditional exports中还支持自定义conditions (https://nodejs.org/api/packages.html#resolving-user-conditions)。通过这个配置进行支持。
当我们需要import
一个模块es-module-package
,它的package.json
是下面这样定义的话
{ // ... "exports": { ".": { "my-condition": "./foo.mjs", "node": "./bar.mjs", "import": "./baz.mjs", "require": "./biz.mjs" } } }
import feature from 'es-module-package/my-condition'; // 期待加载./node_modules/es-module-package/foo.mjs
那么tsconfig.json
可以这么写
{ "compilerOptions": { "target": "es2022", "moduleResolution": "bundler", "customConditions": ["my-condition"] } }
当--moduleResolution
是node16
、nodenext
、bundler
是,配置默认启用。
verbatimModuleSyntax
提供了一个新的配置项,用于简化之前 TypeScript 的引用省略功能涉及importsNotUsedAsValues
、preserveValueImports
、isolatedModules
三个配置项的配置复杂问题。
支持 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 为组织和排序导入和导出提供支持。不过,对于何时排序的列表会有不同的解释。
例如,以下导入列表是否已排序?
import { Toggle, freeze, toBoolean, } from "./utils";
令人惊讶的是,答案可能是“取决于情况”。如果我们不关心大小写,那么这个列表显然没有排序。字母 "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
条目来选择它们。以下是您可以尝试的所有选项(默认设置):
{ "typescript.unstable": { // Should sorting be case-sensitive? Can be: // - true // - false // - "auto" (auto-detect) "organizeImportsIgnoreCase": "auto", // Should sorting be "ordinal" and use code points or consider Unicode rules? Can be: // - "ordinal" // - "unicode" "organizeImportsCollation": "ordinal", // Under `"organizeImportsCollation": "unicode"`, // what is the current locale? Can be: // - [any other locale code] // - "auto" (use the editor's locale) "organizeImportsLocale": "en", // Under `"organizeImportsCollation": "unicode"`, // should upper-case letters or lower-case letters come first? Can be: // - false (locale-specific) // - "upper" // - "lower" "organizeImportsCaseFirst": false, // Under `"organizeImportsCollation": "unicode"`, // do runs of numbers get compared numerically (i.e. "a1" < "a2" < "a100")? Can be: // - true // - false "organizeImportsNumericCollation": true, // Under `"organizeImportsCollation": "unicode"`, // do letters with accent marks/diacritics get sorted distinctly // from their "base" letter (i.e. is é different from e)? Can be // - true // - false "organizeImportsAccentCollation": true }, "javascript.unstable": { // same options valid here... }, }
完善的 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
关键字偶尔规避在闭包中使用let
和const
所产生的成本,这提高了我们的一些解析性能。
总的来说,我们期望大多数代码库应该会从 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/