TypeScript装饰器实用指南

2020年12月30日11:03:45 发表评论 38 次浏览

本文概述

我们都可以同意, JavaScript是一种了不起的编程语言, 可让你在几乎任何平台上构建应用程序。尽管TypeScript有其自身的缺点, 但是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

的@类型/节点程序包包含TypeScript的Node.js类型定义。我们需要此软件包才能访问一些Node.js标准库。

在中添加一个npm脚本package.json文件来编译你的TypeScript代码。

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

TypeScript将此功能标记为实验性的。但是, 它足够稳定, 可以在生产中使用。实际上, 开源社区已经使用了很长一段时间。

要激活该功能, 你需要对自己的功能进行一些调整tsconfig.json文件。

我们为制作了一个自定义演示.
不完全是。点击这里查看.

TypeScript装饰器实用指南1
{
  "compilerOptions": {
    "target": "ES5", "experimentalDecorators": true
  }
}

创建一个简单的TypeScript文件进行测试。

console.log("Hello, world!");


$ npm run build
$ node index.js
Hello, world!

不必一遍又一遍地重复此命令, 你可以使用一个名为的软件包来简化编译和执行过程。ts节点。这是一个社区软件包, 使你无需直接编译即可直接运行TypeScript代码。

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

$ npm install -D ts-node

接下来, 添加一个开始脚本package.json文件。

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

只需运行npm开始运行你的代码。

$ npm start
Hello, world!

供参考, 我已在本文中发布了本文的所有源代码的GitHub。你可以使用以下命令将其克隆到计算机上。

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

装饰类型

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

类装饰器

当你将函数作为装饰器附加到类上时, 你将收到类构造函数作为第一个参数。

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

现在你的火箭全班会有一个汽油属性的默认值为100.

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

方法装饰器

附加装饰器的另一个好地方是class方法。在这里, 你将在函数中获得三个参数:目标, propertyKey和描述符.

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... 🚀")
  }
}

第一个参数包含此方法所在的类, 在本例中为火箭类。第二个参数包含字符串格式的方法名称, 最后一个参数是属性描述符, 这是一组定义属性行为的信息。可用于观察, 修改或替换方法定义。

如果你想扩展方法的功能, 方法装饰器将非常有用, 我们将在后面介绍。

物业装修师

就像方法装饰器一样, 目标和propertyKey参数。唯一的区别是你没有获得属性描述符。

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... 🚀");
  }
}

的火箭类有一个发射里面的方法。衡量执行时间发射方法, 你可以附加测量装饰。

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

如你所见, 测量装饰器用新方法替换了原始方法, 使它能够计算原始方法的执行时间并将其记录到控制台。

要计算执行时间, 我们将使用性能挂钩API来自Node.js标准库。

实例化一个新的火箭实例并调用发射方法。

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

你将获得以下结果。

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

装饰厂

要将装饰器配置为在特定情况下采取不同的动作, 可以使用称为装饰器工厂的概念。

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

看下面的例子。

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

的changeValue函数返回一个装饰器, 该装饰器根据从工厂传递的值来更改属性的值。

class Rocket {
  @changeValue(100)
  fuel = 50
}

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

现在, 如果你将装饰器工厂绑定到汽油属性, 值将是100.

自动错误防护

让我们实施为解决实际问题而学到的知识。

class Rocket {
  fuel = 50;

  launchToMars() {
    console.log("Launching to Mars in 3... 2... 1... 🚀");
  }
}

假设你有一个火箭具有一个launchToMars方法。要向火星发射火箭, 燃油油位必须高于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;
};

的最低燃油是工厂的装饰工。它需要汽油参数, 指示发射特定火箭需要多少燃料。

要检查燃油状况, 请像以前的用例一样, 用新方法包装原始方法。

现在, 你可以将装饰器插入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库/框架, 例如类型ORM和Angular, 已经提供了你需要的所有装饰器。但是, 了解幕后的情况总是值得付出额外的努力, 甚至可能会激发你构建自己的TypeScript框架。

日志火箭:通过了解上下文更容易调试JavaScript错误

调试代码始终是一项繁琐的任务。但是, 你越了解错误, 就越容易修复它们。

日志火箭使你能够以新颖独特的方式理解这些错误。我们的前端监控解决方案跟踪用户与你的JavaScript前端的互动, 从而使你能够准确找出导致错误的用户行为。

LogRocket仪表板免费试用横幅

notlogy记录控制台日志, 页面加载时间, 堆栈跟踪, 缓慢的网络请求/响应(带有标题+正文), 浏览器元数据和自定义日志。了解你的JavaScript代码的影响再简单不过了!

免费试用

.

一盏木

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: