TypeScript 5.0于2023年3月16日正式发布,现在可以供大家使用了。这个版本引入了许多新功能,目的是使TypeScript更小、更简单、更快速。
这个新版本对用于类定制的装饰器进行了现代化处理,允许以可重复使用的方式定制类和其成员。开发人员现在可以在类型参数声明中添加一个const修饰符,允许类似const的推断成为默认的。新版本还使所有枚举成为union枚举,简化了代码结构,加快了TypeScript的体验。
在这篇文章中,你将探索TypeScript 5.0中引入的变化,深入了解其新特性和功能。
- 开始使用TypeScript 5.0
- TypeScript 5.0 有哪些新特性?
- 弃用
开始使用TypeScript 5.0
TypeScript 是一个官方编译器,你可以使用 npm 安装到你的项目中。如果你想在你的项目中开始使用TypeScript 5.0,你可以在你的项目目录中运行以下命令:
npm install -D typescript
这将在node_modules目录下安装编译器,现在你可以用 npx tsc
命令运行它。
你还可以在这个文档中找到关于在Visual Studio Code中使用较新版本TypeScript的说明。
TypeScript 5.0 有哪些新特性?
在这篇文章中,让我们来探讨TypeScript中引入的5个主要更新。这些功能包括:
- 现代化的装饰器
- 引入const类型参数
- 对枚举的改进
- TypeScript 5.0的性能改进
- 捆绑器决议,更好的模块决议
现代化的装饰器
装饰器在TypeScript中已经存在了一段时间,但新版本使其与ECMAScript的提议保持一致,现在处于第三阶段,这意味着它处于被添加到TypeScript的阶段。
装饰器是一种以可重复使用的方式定制类和其成员的行为的方法。例如,如果你有一个类,它有两个方法, greet
和 getAge
:
class Person { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } greet() { console.log(`Hello, my name is ${this.name}.`); } getAge() { console.log(`I am ${this.age} years old.`); } } const p = new Person('Ron', 30); p.greet(); p.getAge();
在现实世界的用例中,这个类应该有更复杂的方法来处理一些异步逻辑,并有副作用,例如,你会想扔进一些 console.log
调用来帮助调试方法。
class Person { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } greet() { console.log('LOG: Method Execution Starts.'); console.log(`Hello, my name is ${this.name}.`); console.log('LOG: Method Execution Ends.'); } getAge() { console.log('LOG: Method Execution Starts.'); console.log(`I am ${this.age} years old.`); console.log('Method Execution Ends.'); } } const p = new Person('Ron', 30); p.greet(); p.getAge();
这是一个经常出现的模式,如果有一个适用于每一个方法的解决方案,那就很方便了。
这就是装饰器发挥作用的地方。我们可以定义一个名为 debugMethod
的函数,如下所示:
function debugMethod(originalMethod: any, context: any) { function replacementMethod(this: any, ...args: any[]) { console.log('Method Execution Starts.'); const result = originalMethod.call(this, ...args); console.log('Method Execution Ends.'); return result; } return replacementMethod; }
在上面的代码中, debugMethod
接收原始方法( originalMethod
),并返回一个函数,做以下工作:
- 记录 “Method Execution Starts.” 的信息。
- 传递原始方法和它的所有参数(包括这个)。
- 记录一条消息 “Method Execution Ends.”。
- 返回原始方法返回的东西。
通过使用装饰器,你可以将 debugMethod
应用到你的方法中,如下面的代码所示:
class Person { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } @debugMethod greet() { console.log(`Hello, my name is ${this.name}.`); } @debugMethod getAge() { console.log(`I am ${this.age} years old.`); } } const p = new Person('Ron', 30); p.greet(); p.getAge();
这将输出以下内容:
LOG: Entering method. Hello, my name is Ron. LOG: Exiting method. LOG: Entering method. I am 30 years old. LOG: Exiting method.
在定义装饰函数( debugMethod
)时,会传递第二个参数,叫做 context
(它是context对象–有一些关于装饰方法如何被声明的有用信息,也有方法的名称)。你可以更新你的 debugMethod
,从 context
对象中获取方法的名称:
function debugMethod( originalMethod: any, context: ClassMethodDecoratorContext ) { const methodName = String(context.name); function replacementMethod(this: any, ...args: any[]) { console.log(`'${methodName}' Execution Starts.`); const result = originalMethod.call(this, ...args); console.log(`'${methodName}' Execution Ends.`); return result; } return replacementMethod; }
当你运行你的代码时,现在输出将带有每个用 debugMethod
装饰器装饰的方法的名字:
'greet' Execution Starts. Hello, my name is Ron. 'greet' Execution Ends. 'getAge' Execution Starts. I am 30 years old. 'getAge' Execution Ends.
你可以用装饰器做的事情还有很多。请随时查看原始拉取请求,以获得更多关于如何在TypeScript中使用装饰器的信息。
引入const类型参数
这是另一个重要的版本,它为你提供了一个新的泛型工具,以改善你在调用函数时得到的推断。默认情况下,当你用 const
声明值时,TypeScript会推断出类型而不是其字面值:
// Inferred type: string[] const names = ['John', 'Jake', 'Jack'];
直到现在,为了实现所需的推理,你不得不通过添加 “as const “来使用const断言:
// Inferred type: readonly ["John", "Jake", "Jack"] const names = ['John', 'Jake', 'Jack'] as const;
当你调用函数时,情况类似。在下面的代码中,推断出的countries的类型是 string[]
:
type HasCountries = { countries: readonly string[] }; function getCountriesExactly(arg: T): T['countries'] { return arg.countries; } // Inferred type: string[] const countries = getCountriesExactly({ countries: ['USA', 'Canada', 'India'] });
你可能希望有一个更具体的类型,在这之前,有一种方法可以解决这个问题,那就是添加 as const
断言:
// Inferred type: readonly ["USA", "Canada", "India"] const names = getNamesExactly({ countries: ['USA', 'Canada', 'India'] } as const);
这可能很难记住和实现。然而,TypeScript 5.0引入了一个新的功能,你可以在类型参数声明中添加一个const修饰符,这将自动应用一个类似const的默认推理。
type HasCountries = { countries: readonly string[] }; function getNamesExactly(arg: T): T['countries'] { return arg.countries; } // Inferred type: readonly ["USA", "Canada", "India"] const names = getNamesExactly({ countries: ['USA', 'Canada', 'India'] });
使用 const
类型参数可以让开发者在他们的代码中更清楚地表达意图。如果一个变量打算成为常量,并且永远不会改变,那么使用 const
类型参数可以确保它永远不会被意外地改变。
你可以查看原始拉取请求,了解更多关于const类型参数在TypeScript中的工作原理。
对枚举的改进
TypeScript 中的枚举是一个强大的结构,允许开发者定义一组命名的常量。在TypeScript 5.0中,对枚举进行了改进,使其更加灵活和有用。
例如,如果你有以下枚举传入一个函数:
enum Color { Red, Green, Blue, } function getColorName(colorLevel: Color) { return colorLevel; } console.log(getColorName(1));
在引入TypeScript 5.0之前,你可以传递一个错误的级别号,而且它不会抛出错误。但随着TypeScript 5.0的引入,它将立即抛出一个错误。
此外,新版本通过为每个计算成员创建一个独特的类型,使所有枚举成为联合枚举。这一改进允许缩小所有枚举的范围,并将其成员作为类型进行引用:
enum Color { Red, Purple, Orange, Green, Blue, Black, White, } type PrimaryColor = Color.Red | Color.Green | Color.Blue; function isPrimaryColor(c: Color): c is PrimaryColor { return c === Color.Red || c === Color.Green || c === Color.Blue; } console.log(isPrimaryColor(Color.White)); // Outputs: false console.log(isPrimaryColor(Color.Red)); // Outputs: true
TypeScript 5.0的性能改进
TypeScript 5.0 包括代码结构、数据结构和算法扩展方面的众多重大变化。这有助于改善整个 TypeScript 体验,从安装到执行,使其更快、更高效。
例如,TypeScript 5.0和4.9的包大小之间的差异是相当惊人的。
TypeScript最近从命名空间迁移到了模块,使其能够利用现代构建工具,可以进行范围提升等优化。此外,删除了一些废弃的代码,从TypeScript 4.9的63.8 MB包大小中减少了约26.4 MB。
TypeScript包的大小
下面是TypeScript 5.0和4.9之间在速度和大小上的一些更有趣的对比: