首页IT科技typescript 4.4(带你看看 TypeScript 5.0 的新特性)

typescript 4.4(带你看看 TypeScript 5.0 的新特性)

时间2025-05-01 16:25:29分类IT科技浏览4125
导读:一、写在前面 TypeScript 5.0 已经于 2023 年 3 月 16 日发布了,带来了许多新功能,同时也在性能方面进行了优化,下面让我们来一起看看新版 TypeScript 中比较有重要的变化吧。...

一           、写在前面

TypeScript 5.0 已经于 2023 年 3 月 16 日发布了           ,带来了许多新功能                ,同时也在性能方面进行了优化      ,下面让我们来一起看看新版 TypeScript 中比较有重要的变化吧           。

二                 、新特性

2-1     、速度           、包体积优化

首先是新版本性能的提升      ,5.0 版本在构建速度                 、包体积方面都有着不错的优化                ,下面这张表格是 5.0 版本相对于 4.9 的性能提升幅度:

项目 相对于 TS 4.9 的优化幅度 material-ui 构建时间 90% TypeScript 编译器启动时间 89% TypeScript 编译器自构建时间 87% Outlook Web 构建时间 82% VS Code 构建时间 80% npm 包大小 59%

TypeScript 5.0 具体做了什么来优化性能呢?

首先是将namespace迁移到module           ,这样可以应用更多现代构建工具的特性来进行优化(如作用域提升)      ,并移除一些已弃用的代码                 ,将包大小减少了约 26.4 MB                 。 其次是 TS 精简了编译器内部的对象所存储的数据           ,减少了内存使用量     。 然后是在一些特定领域进行优化,如在闭包中偶尔使用var                 ,而不是let或const来提高解析性能           。

总的来说                 ,大多数 TypeScript 项目都能从 TypeScript 5.0 中获得 10% 到 20% 性能提升                 。

2-2     、新的装饰器标准

装饰器这个东西,写 ts 的小伙伴一定不会陌生           ,虽然它还不是一个标准的 js 特性                 ,但 ts 已经在之前的版本就支持了“实验性           ”装饰器     。而在最新的 5.0 版本中      ,装饰器语法将不再是一个“实验性                 ”的语法           ,也不需要在编译选项中加入--experimentalDecorators配置项了(虽然在新版本                ,这个编译选项依旧会存在)      ,TypeScript 5.0 将会原生支持装饰器语法!

下面让我们来看一个装饰器的例子吧——

首先我们有一个很简单的类:

class Person { name: string; constructor(name: string) { this.name = name; } greet() { console.log(`Hello, my name is ${this.name}.`); } } const p = new Person("zy"); p.greet();

我们想在这个类的greet函数中加点日志      ,并记录调用函数的名称                ,那么最简单的方式是这样做:

class Person { name: string; constructor(name: string) { this.name = name; } greet() { console.log("LOG: Entering method greet."); console.log(`Hello, my name is ${this.name}.`); console.log("LOG: Exiting method greet."); } } const p = new Person("zy"); p.greet();

但如果我们想给更多的函数都加上类似的功能           ,那用装饰器就非常合适了      ,比如我们可以编写一个loggedMethod                 ,如下所示:

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; }

这样我们用loggedMethod这个装饰器来装饰这个函数           ,就可以实现上述效果了:

class Person { name: string; constructor(name: string) { this.name = name; } @loggedMethod greet() { console.log(`Hello, my name is ${this.name}.`); } } const p = new Person("zy"); p.greet(); // Output: // // LOG: Entering method greet. // Hello, my name is zy. // LOG: Exiting method greet.

我们甚至可以创建一个“返回装饰器函数的函数     ”,这样我们就可以为装饰器开发更多定制化更多的功能                 ,比如说                 ,我想自定义输出到控制台的字符串的前缀:

function loggedMethod(headMessage = "LOG:") { return function actualDecorator( originalMethod: any, context: ClassMethodDecoratorContext ) { const methodName = String(context.name); function replacementMethod(this: any, ...args: any[]) { console.log(`${headMessage} Entering method ${methodName}.`); const result = originalMethod.call(this, ...args); console.log(`${headMessage} Exiting method ${methodName}.`); return result; } return replacementMethod; }; }

这样我们在loggedMethod被用作装饰器之前调用它,就可以实现传入定制的字符串作为控制台输出字符串的前缀了:

class Person { name: string; constructor(name: string) { this.name = name; } @loggedMethod("LOG:") greet() { console.log(`Hello, my name is ${this.name}.`); } } const p = new Person("zy"); p.greet(); // Output: // // LOG: Entering method greet. // Hello, my name is zy. // LOG: Exiting method greet.

2-3      、const类型参数

在推断对象的类型时           ,ts 通常会选择一种通用的类型      。例如                 ,在这种情况下      ,会将names的类型推断为string []:

type HasNames = { readonly names: string[] }; function getNamesExactly<T extends HasNames>(arg: T): T["names"] { return arg.names; } // Inferred type: string[] const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"] });

推断成string []固然没有问题           ,但由于names是readonly的                ,而推断出的类型不是readonly的      ,这就会产生一些困扰                 。虽然我们可以通过添加as const来解决这个问题      ,就像这样:

// The type we wanted: // readonly ["Alice", "Bob", "Eve"] // The type we got: // string[] const names1 = getNamesExactly({ names: ["Alice", "Bob", "Eve"] }); // Correctly gets what we wanted: // readonly ["Alice", "Bob", "Eve"] const names2 = getNamesExactly({ names: ["Alice", "Bob", "Eve"] } as const);

但这样写很麻烦                ,且容易被忘掉           。所以在 TypeScript 5.0 中           ,我们可以将const修饰符直接添加到类型参数声明中      ,将常数类型推理变为默认值:

type HasNames = { names: readonly string[] }; function getNamesExactly<const T extends HasNames>(arg: T): T["names"] { // ^^^^^ return arg.names; } // Inferred type: readonly ["Alice", "Bob", "Eve"] // Note: Didnt need to write as const here const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"] });

具体细节:https://github.com/microsoft/TypeScript/pull/51865

2-4                 、extends配置项支持多个配置文件

extends配置项支持多个配置文件其实是我个人认为 TypeScript 5.0 中最实用的一个特性了      。当我们使用tsconfig.json管理多个项目时                 ,很多情况下都会在一个“基准           ”配置文件上进行配置扩展           ,比如下面这样:

// packages/front-end/src/tsconfig.json { "extends": "../../../tsconfig.base.json", "compilerOptions": { "outDir": "../lib", // ... } }

但是,在很多时候                 ,我们会希望从多个配置文件进行扩展                 ,但 TypeScript 4.9 及以前的版本都不支持这个功能!而好消息是,Typescript 5.0 现在允许该 extends 字段引入多个配置路径                 。例如           ,下面这种写法:

// tsconfig1.json { "compilerOptions": { "strictNullChecks": true } } // tsconfig2.json { "compilerOptions": { "noImplicitAny": true } } // tsconfig.json { "extends": ["./tsconfig1.json", "./tsconfig2.json"], "files": ["./index.ts"] }

在这个例子中                 ,strictNullChecks和noImplicitAny都会在最终的tsconfig.json中生效           。

注意      ,如果这些引入的配置文件中有字段冲突           ,那么后引入的字段会覆盖先引入的字段。

具体细节:https://github.com/microsoft/TypeScript/pull/50403

2-5           、所有枚举变为联合枚举

TypeScript 最初在设计枚举类型时                ,只不过是将它们视为一组具有相同类型的数字常量      ,比如下面这个枚举E:

enum E { Foo = 10, Bar = 20, }

E.Foo      、E.Bar相比于普通的变量      ,唯一特别之处在于它可以分配给任何类型为E的东西                 。除此之外                ,他们几乎和numbers类型没什么区别           ,就像下面这样:

function takeValue(e: E) {} takeValue(E.Foo); // works takeValue(123); // error!

直到 TypeScript 2.0 引入了枚举文字类型      ,枚举才变得特殊                 。枚举文字类型为每个枚举成员提供了自己的类型                 ,并将枚举本身变成了每个成员类型的集合。它们还允许我们仅引用枚举类型的一个子集           ,并缩小这些类型的范围,就如同下面展示的枚举Color:

// Color is like a union of Red | Orange | Yellow | Green | Blue | Violet enum Color { Red, Orange, Yellow, Green, Blue, /* Indigo */, Violet } // Each enum member has its own type that we can refer to! type PrimaryColor = Color.Red | Color.Green | Color.Blue; function isPrimaryColor(c: Color): c is PrimaryColor { // Narrowing literal types can catch bugs. // TypeScript will error here because // well end up comparing Color.Red to Color.Green. // We meant to use ||, but accidentally wrote &&. return c === Color.Red && c === Color.Green && c === Color.Blue; }

而在某些场景——比如枚举成员使用函数调用初始化的时候                 ,TypeScript 无法计算出枚举值的集合                 ,它就会放弃联合枚举,转而使用旧的枚举策略           。

而 TypeScript 5.0 解决了这个问题           ,它通过为每个枚举成员创建唯一类型                 ,将所有枚举都变成联合枚举                 。这样      ,我们在所有情况下的枚举值           ,都将是联合枚举     。

具体细节:https://github.com/microsoft/TypeScript/pull/50528

2-6                 、--moduleResolution配置项支持bundler选项

TypeScript 4.7 在--module和--moduleResolution配置项中引入了node16和nodenext选项           。这些选项能更好地模拟 Node.js 中 ECMAScript 模块的查找规则                 。然而                ,这种模式有很多限制      ,例如      ,在 Node.js 的 ECMAScript 模块中                ,任何相对导入都需要包含文件扩展名:

// entry.mjs import * as utils from "./utils"; // wrong - we need to include the file extension. import * as utils from "./utils.mjs"; // works

但随着前端技术的发展           ,这种查找规则已经落伍了     。大多数现代打包工具在 Node.js 中使用 ECMAScript 模块和 CommonJS 模块查找规则的融合      。所以为了模拟打包工具的工作方式      ,TypeScript 现在引入了一种新策略:--moduleResolution bundler.

{ "compilerOptions": { "target": "esnext", "moduleResolution": "bundler" } }

如果你在使用像 Vite           、esbuild、swc                 、Webpack                 、Parcel 等打包工具                 ,那么bundler这个配置会非常合适                 。

具体细节:https://github.com/microsoft/TypeScript/pull/51669

2-7、支持export type *

在 TypeScript 3.8 引入类型导入/导出时           ,是不允许 export _ from "module" 或 export _ as xx from "module" 这样的类型导出的           。TypeScript 5.0 添加了对这两种类型导出语法的支持:

// models/vehicles.ts export class Spaceship { // ... } // models/index.ts export type * as vehicles from "./vehicles"; // main.ts import { vehicles } from "./models"; function takeASpaceship(s: vehicles.Spaceship) { // ok - `vehicles` only used in a type position } function makeASpaceship() { return new vehicles.Spaceship(); // ^^^^^^^^ // vehicles cannot be used as a value because it was exported using export type. }

详情请见:https://github.com/microsoft/TypeScript/pull/52217

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
authentication-profile name命令(aulastlog命令 – 打印最近登陆记录) 网站优化关键词选择的原则和方法(优化SEO关键词:提升网站排名的终极指南)