likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 23

本文作者是蚂蚁集团前端工程师厚发,基于《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 调用。在这些发行说明中,我们也提供了一些上下文信息,建议大多数库作者使用 node16nodenext

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

自我们发布 RC 版本以来,最显着的变化是 TypeScript 5.0 现在在 package.json 中指定了 Node.js 的最低版本为 12.20。我们还发布了一篇有关 TypeScript 5.0 迁移到模块的文章,并链接到了它。

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

BreakChanges And Deprecations

运行时要求

要求 Node.js 10.x 以上。

lib.d.ts变更

例行环节。具体变更在这:lib.d.ts change 后面实际项目中如果有遇到一些常用的用法报错了,需要手动更改代码的 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方法执行时机

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

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

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

const p = new Person("Ray");

instanceMethodinstanceFiledDecorator中通过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 也支持修饰器,需要增加--experimentalDecorators编译选项。

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

TypeScript5.0 的修饰器标准跟之前的元数据反射是不兼容的。

写类型完备的修饰器

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

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

// 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.aE_1.c这样(使用数字常量初始化或带有数字常量表达式)的枚举成员,是「计算枚举成员」呢还是「常量枚举成员」。

感觉「计算枚举成员」和「常量枚举成员」的定义 要修改,到目前官方文档还没修改。

moduleResolution配置新增bundler支持

TypeScript 4.7--module--moduleResolution增加了node16nodenext,从而更好地在 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这两个配置一起启用。

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

resolvePackageJsonExports与resolvePackageJsonImports

配置启用后,import来自node_modules中的模块时,TypeScript 会去解析模块对应的package.json中的exportsimports字段。这块可以去看一下 Conditional exports 的知识。

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

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 等样式文件,我可能还是会这样处理,感觉加上类型没太大必要。

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

customConditions

Conditional exports中还支持自定义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"]
    }
}

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

verbatimModuleSyntax

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

支持 export type *

支持使用export * from "module"export * as ns from "module"这样的语句进行类型导出

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 字符编码,大写字母在小写字母之前。因此,从这个角度来看,导入列表已排序。

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

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

我们的团队还在尝试更多的排序策略,您可以在此处阅读更多信息。这些选项可能最终可以由编辑器进行配置。目前,它们仍然是不稳定和实验性的,您可以通过在 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 代码补全

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

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

官方称 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。这也通过直接函数调用带来了显着的加速。我们在这里写了一篇关于我们迁移到模块的详细说明

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

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

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

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

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

参考

转载自:https://juejin.cn/post/7212529038875426875
评论
请登录