6分钟搞定TypeScript装饰器

发布时间:2024年01月05日

图片

原来TypeScript的装饰器并不难,一篇文章让你掌握4种类型的装饰器!

如果你使用过Angular或Nest.js,你应该熟悉装饰器。

import { Controller, Get } from '@nestjs/common'
@Controller('cats')export class CatsController {  @Get()  findAll(): string {    return 'This action returns all cats'  }}

在上面的代码中?@XXX?是语法糖,要启用实验性装饰器功能,您必须在命令行或?tsconfig.json?文件中启用?experimentalDecorators?编译器选项。???????

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


装饰器主要有四种类型:

  • 类装饰器

  • 属性装饰器

  • 方法装饰器

  • 参数装饰器
    ?

接下来,我将分别为大家介绍这4类装饰器。

类装饰器
?

类装饰器,顾名思义,是用来装饰类的。类装饰器对应的类型在?lib.es5.d.ts?文件中定义:???????

// node_modules/typescript/lib/lib.es5.d.tsdeclare type ClassDecorator = <TFunction extends Function>  (target: TFunction) => TFunction | void;

从类装饰器的类型定义可以看出,它接收一个表示要装饰的类的参数。根据类decorator的定义,我们可以创建一个?Greeter??decorator来为被装饰的类添加一个?greet?方法:???????

// user.tsfunction Greeter(target: Function): void {  target.prototype.greet = function (): void {    console.log("Hello Bytefer!");  };}
@Greeterclass User {}
let bytefer = new User();(bytefer as any).greet();

当您成功运行上述代码时,终端将输出结果-??Hello Bytefer!?。类装饰器看起来很简单,但问题来了。如果我们想要定制?greet?方法的输出,我们应该怎么做?为了满足这个功能,我们需要使用装饰器工厂。
?

装饰器工厂
?

所谓装饰器工厂就是在被调用后返回装饰器。利用高阶函数的特性,我们来更新前面定义的?Greeter?函数:???????

function Greeter(msg: string) {   return (target: Function): void => {    target.prototype.greet = function (): void {        console.log(msg);    };   }}

在更新?Greeter?装饰器工厂后,我们需要按照以下方式使用它:???????

@Greeter("Hello TypeScript!")class User {}
let bytefer = new User();(bytefer as any).greet();

当您成功运行上述代码时,终端将输出结果-??Hello TypeScript!?。应该注意的是,我们可以为同一个类使用多个类装饰器。例如,在下面的代码中,我添加了一个?Log?类装饰器:

function Log(target: Function): void {  target.prototype.log = (msg: string) => {    console.log(`From ${target.name}: `, msg);  };}
@Log@Greeter("Hello TypeScript!")class User {}
let bytefer = new User();(bytefer as any).greet();(bytefer as any).log("Hello Kakuqo!");

在上面的代码中,我们为?User?类添加了2个装饰器,之后,我们可以在?User?实例上调用?greet?和?log?方法。

Hello TypeScript!From User:  Hello Kakuqo!


属性装饰器
?

属性装饰器用于装饰类的属性。其对应的类型声明如下:???????

// node_modules/typescript/lib/lib.es5.d.tsdeclare type PropertyDecorator =  (target: Object, propertyKey: string | symbol) => void;

属性装饰函数有两个参数:?target?和?property?。根据属性装饰器的定义,让我们定义一个属性装饰器?logProperty?来跟踪用户对属性的操作。???????

function logProperty(target: any, key: string) {  let value = target[key];
  const getter = function () {    console.log(`Getter for ${key} returned ${value}`);    return value;  };
  const setter = function (newVal: any) {    console.log(`Set ${key} to ${newVal}`);    value = newVal;  };
  // Replace the property  if (delete target[key]) {    Object.defineProperty(target, key, {      get: getter,      set: setter,      enumerable: true,      configurable: true,    });  }}

一旦我们有了?logProperty?属性装饰器,我们就可以把它应用到类的属性上。例如,我在?User?类的?name?属性上使用了?logProperty?装饰符:???????

class User {  @logProperty  public name: string;
  constructor(name: string) {    this.name = name;  }}
const user = new User("Bytefer");user.name = "Kakuqo";console.log(user.name);

当您成功运行上述代码时,终端将输出结果:???????

Set name to ByteferSet name to KakuqoGetter for name returned KakuqoKakuqo

除了能够修饰类的属性外,我们还可以装饰类的方法。
?

方法装饰器
?

方法装饰器用于装饰类的方法。其对应的类型声明如下:???????

// node_modules/typescript/lib/lib.es5.d.tsdeclare type MethodDecorator = <T>(  target: Object,   propertyKey: string | symbol,   descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;

与属性装饰器相比,方法装饰器多了一个描述符参数。参数类型如下:???????

interface TypedPropertyDescriptor<T> {    enumerable?: boolean;    configurable?: boolean;    writable?: boolean;    value?: T;    get?: () => T;    set?: (value: T) => void;}

如果你不需要使用泛型,你也可以使用?PropertyDescriptor?接口:???????

interface PropertyDescriptor {    configurable?: boolean;    enumerable?: boolean;    value?: any;    writable?: boolean;    get?(): any;    set?(v: any): void;}

根据方法装饰器的定义,我们定义一个方法装饰器?logMethod?来跟踪类成员方法的调用。???????

function logMethod(  target: Object,  propertyKey: string,  descriptor: PropertyDescriptor) {  let originalMethod = descriptor.value;  descriptor.value = function (...args: any[]) {    console.log(`Before invoking method: ${propertyKey}`);    let result = originalMethod.apply(this, args);    console.log(`After invoking method: ${propertyKey}`);    return result;  };}

一旦有了?logMethod?方法装饰符,就可以将它应用到类的成员方法上。例如,我在?User?类的?greet?成员方法上使用了?logMethod?装饰符:???????

class User {  @logMethod  greet(msg: string): string {    return `Hello ${msg}!`;  }}
let user = new User();let msg = user.greet("Bytefer");console.log(msg);

当您成功运行上述代码时,终端将输出结果:???????

Before invoking method: greetAfter invoking method: greetHello Bytefer!

掌握了方法装饰器之后,我们可以定义一些有用的方法装饰器。如?delay?、?throttle?等。

延迟???????

function delay(milliseconds: number = 0): any {  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {    const originalMethod = descriptor.value;
    descriptor.value = function(...args) {      setTimeout(() => {        originalMethod.apply(this, args);      }, milliseconds);    };    return descriptor;  };}

节流???????

const throttleFn = require('lodash.throttle');
function throttle(milliseconds: number = 0, options = {}): any {  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {    const originalMethod = descriptor.value;    descriptor.value = throttleFn(originalMethod, milliseconds, options);    return descriptor;  };}

最后,让我们介绍参数装饰器。
???????

参数装饰器
?

参数装饰器用于装饰方法中的参数。其对应的类型声明如下:???????

// node_modules/typescript/lib/lib.es5.d.tsdeclare type ParameterDecorator = ( target: Object,  propertyKey: string | symbol,  parameterIndex: number) => void;

根据参数decorator的定义,我们定义一个方法decorator??logParameter?:???????

function logParameter(target: Object, key: string, parameterIndex: number) {  console.log(`The parameter in position ${parameterIndex} at ${key} has been decorated`);}

有了?logParameter?装饰符,我们将它应用到?User?类中?greet?方法的msg参数:???????

class User {  greet(@logParameter msg: string): void {    console.log(msg);  }}
let user = new User();user.greet("Bytefer");

当您成功运行上述代码时,终端将输出结果:???????

The parameter in position 0 at greet has been decoratedBytefer

既然已经介绍了这4个装饰器,如果你想知道装饰器是如何工作的,你可以使用TypeScript Playground来查看编译后的JavaScript代码。

图片

在开发装饰器时,我们通常还使用reflect-metadata库来处理元信息。例如,开发IoC容器或扩展web框架的功能。如果你想了解更多关于装饰器的知识,我建议你阅读overnight项目的源代码(ExpressJS服务器的TypeScript装饰器),它比Nest.js框架更容易理解。

?欢迎关注公众号:文本魔术,了解更多

文章来源:https://blog.csdn.net/wannianchuan/article/details/135399798
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。