Typescript修饰器指南
JavaScript是一种惊人的编程语言,允许您在几乎任何平台上构建应用程序。但是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
方法装饰器
附加装饰器的另一个好地方是类的方法。 在这里,您可以在函数中获得三个参数:target
,propertyKey
和descriptor
。
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
类。 第二个参数包含方法名称字符串,最后一个参数是属性描述符,这是一个定义属性行为的一组信息。 这可用于观察,修改或替换方法定义。
如果要扩展方法的功能,则该方法装饰器可能非常有用,我们将稍后介绍。
属性装饰器
就像方法装饰器一样,您将获得target
和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... 🚀");
}
}
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框架。