7

Typescript装饰器指南 | myfreax

 3 years ago
source link: https://www.myfreax.com/a-practical-guide-to-typescript-decorators/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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框架。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK