Typescript修饰器指南

文章目录

JavaScript是一种惊人的编程语言,允许您在几乎任何平台上构建应用程序。 虽然它也有着自己的缺点,但是TypeScript 在类型上已经做出了很大的贡献,涵盖了JavaScript中固有的一些差距。 它不仅为动态语言添加了类型的安全性,而且还具有一些很酷的功能,尚未存在于JavaScript中,例如装饰器。

什么装饰器?

虽然定义方式可能因不同的编程语言而异,修饰器是一种在编程中的模式,您可以在其中包装以改变其行为的代码。

在JavaScript中,此功能目前在两个阶段。 它尚未在浏览器或Node.js中使用,但您可以使用Babel等编译器来测试它。这不是一个全新的东西。在JavaScript之前,几种编程语言,如Python,Java和C#,采用了此模式。

即使JavaScript已经提出了此功能,TypeScript的装饰器功能以几种重要方式不同。 由于TypeScript是一种强类型的语言,您可以访问与数据类型关联的一些附加信息,以进行一些很酷的动作,例如运行时类型 - 断言和依赖项注入。

入门

首先创建空白Node.js 项目。

$ mkdir typescript-decorators
$ cd typescript decorators
$ npm init -y

接下来,将TypeScript安装为开发依赖项。

$ npm install -D typescript @types/node

@types/node包含Node.js的类型定义。 我们需要这个包来访问Node.js标准库类型定义。

package.json文件中添加NPM脚本以编译您的TypeScript 代码。

{
// ...
"scripts": {
"build": "tsc"
}
}

TypeScript 标记已将此功能标记为实验。 尽管如此,它足以在生产中使用。 事实上,开源社区一直在使用它已有很长一段时间。

要使用该功能,您需要对tsconfig.json文件进行一些调整。

{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}

创建一个简单的TypeScript 文件以测试它。

console.log("Hello, world!");
$ npm run build
$ node index.js
Hello, world!

而不是一遍又一遍地重复此命令,可以使用名为ts-node的包来简化编译和执行过程。 它是一个社区包,使您可以直接运行TypeScript代码而无需首先编译它。

让我们将其安装为开发依赖性。

$ npm install -D ts-node

接下来,将start脚本添加到package.json文件。

{
"scripts": {
"build": "tsc",
"start": "ts-node index.ts"
}
}

只需执行行npm start就可以运行代码。

$ npm start
Hello, world!

以下是一个装饰器参考示例, 您可以使用以下命令将其克隆到计算机上。

$ git clone https://github.com/rahmanfadhil/typescript-decorators.git

装饰器的类型

在TypeScript中,装饰器是可以附加到类及其成员函数,例如方法和属性。 让我们来看看一些例子。

class类装饰器

当您将函数附加到作为装饰器的类class时,您将接收到类的构造函数作为第一个参数。

const classDecorator = (target: Function) => {
// do something with your class
}
@classDecorator
class Rocket {}

如果要覆盖类中的属性,则可以返回的新类中扩展其构造函数并设置属性。

const addFuelToRocket = (target: Function) => {
return class extends target {
fuel = 100
}
}
@addFuelToRocket
class Rocket {}

现在,您的Rocket类将具有fuel属性,其中默认值为100

const rocket = new Rocket()
console.log((rocket).fuel) // 100

方法装饰器

附加装饰器的另一个好地方是类的方法。 在这里,您可以在函数中获得三个参数:targetpropertyKeydescriptor

const myDecorator = (target: Object, propertyKey: string, descriptor: PropertyDescriptor) =>  {
// do something with your method
}
class Rocket {
@myDecorator
launch() {
console.log("Launching rocket in 3... 2... 1... 🚀")
}
}

第一个参数包含此方法所在的类,在这种情况下是Rocket类。 第二个参数包含方法名称字符串,最后一个参数是属性描述符,这是一个定义属性行为的一组信息。 这可用于观察,修改或替换方法定义。

如果要扩展方法的功能,则该方法装饰器可能非常有用,我们将稍后介绍。

属性装饰器

就像方法装饰器一样,您将获得targetpropertyKey参数。 唯一的区别是您没有得到属性描述符。

const propertyDecorator = (target: Object, propertyKey: string) => {
// do something with your property
}

还有其他几个地方可以在TypeScript中连接您的装饰器,但这超出了本文的范围。 如果您很奇怪,您可以在TypeScript文档中了解更多有关它的信息。

使用TypeScript 装饰器案例

现在我们涵盖了装饰器是什么以及如何正确使用它们,让我们来看看一些具体的问题装饰器可以帮助我们解决的。

计算执行时间

假设您想要估计运行函数需要多长时间,以衡量您的应用程序性能。 您可以创建装饰器以计算函数的执行时间并在控制台上打印它。

class Rocket {
@measure
launch() {
console.log("Launching in 3... 2... 1... 🚀");
}
}

Rocket类具有内部的launch方法。 要评估launch方法的执行时间,可以附加measure装饰器。

import { performance } from "perf_hooks";
const measure = (
target: Object,
propertyKey: string,
descriptor: PropertyDescriptor
) => {
const originalMethod = descriptor.value;
descriptor.value = function (...args) {
const start = performance.now();
const result = originalMethod.apply(this, args);
const finish = performance.now();
console.log(`Execution time: ${finish - start} milliseconds`);
return result;
};
return descriptor;
};

正如您所看到的,measure装饰器用新的方法替换原始方法,该方法使其能够计算原始方法的执行时间并将其打印到console控制台。

要计算执行时间,我们将使用Node.js标准库中的性能hook API 。

实例化一个新的Rocket实例并调用launch方法。

const rocket = new Rocket();
rocket.launch();

你会得到以下结果。

Launching in 3... 2... 1... 🚀
Execution time: 1.0407989993691444 milliseconds

装饰器工厂

将装饰器配置为在某种情况下以不同的方式执行,您可以使用一个名为Decorator Factory的概念。

装饰工厂是一个返回装饰器的函数。 这使您可以通过在工厂传递一些参数来自定义装饰器的行为。

看看下面的例子。

const changeValue = (value) => (target: Object, propertyKey: string) => {
Object.defineProperty(target, propertyKey, { value });
};

changeValue函数返回一个装饰器,可以根据从您传递到工厂Factory的值更改属性的值。

class Rocket {
@changeValue(100)
fuel = 50
}
const rocket = new Rocket()
console.log(rocket.fuel) // 100

现在,如果将装饰器工厂绑定到fuel属性,则该值将是100

自动守卫

让我们实现我们学会了什么来解决真实世界的问题。

class Rocket {
fuel = 50;
launchToMars() {
console.log("Launching to Mars in 3... 2... 1... 🚀");
}
}

假设您有一个Rocket类,具有launchToMars方法。 要发射火箭到火星,fuel必须高于100.

为此我们需要创建一个装饰器。

const minimumFuel = (fuel: number) => (
target: Object,
propertyKey: string,
descriptor: PropertyDescriptor
) => {
const originalMethod = descriptor.value;
descriptor.value = function (...args) {
if (this.fuel > fuel) {
originalMethod.apply(this, args);
} else {
console.log("Not enough fuel!");
}
};
return descriptor;
};

minimumFuel是工厂装饰者。 它需要fuel参数,这表示启动特定火箭所需的燃料是多少。

检查燃料条件,用新方法包装原始方法,就像在以前的用例中一样。

现在您可以将装饰器插入launchToMars方法并设置最小燃料级别。

class Rocket {
fuel = 50;
@minimumFuel(100)
launchToMars() {
console.log("Launching to Mars in 3... 2... 1... 🚀");
}
}

现在,如果您调用launchToMars方法,它不会将火箭发射到火星,因为当前的燃油值50.

const rocket = new Rocket()
rocket.launchToMars()
Not enough fuel!

这个装饰器的很酷的事情是您可以将相同的逻辑应用于不同的方法,而无需重写整个 if-else 声明。

你想制作一个新的方法来推动火箭到月球。 要做到这一点,燃料水平必须高于25.

重复相同的代码并更改参数。

class Rocket {
fuel = 50;
@minimumFuel(100)
launchToMars() {
console.log("Launching to Mars in 3... 2... 1... 🚀");
}
@minimumFuel(25)
launchToMoon() {
console.log("Launching to Moon in 3... 2... 1... 🚀")
}
}

现在,这枚火箭可以发射到月球。

const rocket = new Rocket()
rocket.launchToMoon()
Launching to Moon in 3... 2... 1... 🚀

这种类型的装饰器非常有用,可用于认证和授权目的,例如检查是否允许用户访问某些私有数据。

结论

在某些情况下,没有必要编写自己的装饰器。 许多TypeScript库/框架,例如typeorm和 Angular ,已经提供了您需要的所有装饰器。 但是,了解原理总是值得努力的,甚至可能会激励你建立自己的TypeScript框架。

全部为采集文章,文中的 联系方式 均不是 本人 的!

发表评论