TypeScript Decorators装饰器

1,562 阅读4分钟

Decorators(装饰器) 是一种特殊的声明类型,可以使用在 类的声明,方法,访问器,属性和 参数 上;

装饰器的语法形式是:@express , 其中express必须计算为将在运行时调用的函数,其中包含有关装饰声明的信息;

类装饰器(Class Decorators)

类装饰器 放置在类定义之前, 应用到类的构造函数上, 可用于观察,修改和替换类的定义;

类装饰器在运行时作为函数被调用, 并且被装饰的类的构造函数是他唯一的参数

如果类装饰器的函数有返回值(新的构造函数), 那么 他 将用返回的构造函数 来替换原有的 类声明;

  1. Demo 的原型上新增了一个 sayHello 的方法
function demoDecorator<T extends { new (...args: any[]): {} }>(target: T) {
  console.log(target);
}
@demoDecorator
class Demo {
  type = 'demo';
  constructor(private title: string) {}
}
// [class Demo]
  1. 返回值 构造函数发生了变化
function demoDecorator<T extends { new (...args: any[]): {} }>(target: T) {
  console.log(target);
  return class extends target {
    constructor(...args: any[]) {
      super(...args);
    }
    sayHello() {
      console.log('Hello World');
    }
  };
}
@demoDecorator
class Demo {
  type = 'demo';
  constructor(public title: string) {}
}
console.log(Demo); // [class (anonymous) extends Demo]

但是 装饰器不会 更改 定义类的类型, 也就是 你新增的属性或者方法等, 在类型系统中都是未知的,也就是 提示错误 image.png

方法装饰器(Method Decorators)

方法装饰器放置在类的方法定义之前, 被应用于方法的属性描述符上,可用于观察,修改或者替换方法定义;

方法装饰器的表达式将在运行时作为函数调用,并带有以下三个参数:

  1. 静态成员的类的构造函数,或者实例成员的类的原型
  2. 成员名
  3. 成员属性描述符
function MethodDecorator(
  target: any,
  propKey: string,
  descriptor: PropertyDescriptor,
) {
  console.log(target, propKey, descriptor); 
  // {}
  // sayHello
  // {
  //    value: [Function: sayHello],
  //    writable: true,
  //    enumerable: false,
  //    configurable: true
  // }
}
class Demo {
  type = 'demo';
  constructor(public title: string) {}

  @MethodDecorator
  sayHello() {
    console.log('Hello World');
  }
}

如果方法装饰器返回一个值,它将用作该方法的属性描述符。

function MethodDecorator(
  target: any,
  propKey: string,
  descriptor: PropertyDescriptor,
) {
  console.log(target, propKey, descriptor);
// {
//    value: [Function: sayHello],
//    writable: true,
//    enumerable: false,
//    configurable: true
// }
  return {
    enumerable: true,
    value: function () {
      console.log(12);
    },
  };
}
class Demo {
  type = 'demo';
  constructor(public title: string) {}

  @MethodDecorator
  sayHello() {
    console.log('Hello World');
  }
}

console.log(Object.getOwnPropertyDescriptor(Demo.prototype, 'sayHello'));
// {
//   value: [Function: value], // valu值被修改了
//   writable: true,
//   enumerable: true, // enumerable 由 false 变成了 true
//  configurable: true
// }

访问器装饰器(Accessor Decorators)

访问器装饰器在访问器声明之前声明。 访问器装饰器应用于访问器的属性描述符,可用于观察、修改或替换访问器的定义。

访问器装饰器的表达式将在运行时作为函数调用,并带有以下三个参数(跟方法装饰器一样):

  1. 静态成员的类的构造函数,或者实例成员的类的原型
  2. 成员名
  3. 成员属性描述符

如果访问器装饰器返回一个值,它将用作该方法的属性描述符。 注意:不能向多个同名的 get/set 访问器应用修饰器

unction AccessorDecorator(
  target: any,
  propKey: string,
  descriptor: PropertyDescriptor,
) {
  descriptor.configurable = false;
}
class Demo {
  constructor(private _name: string) {}

  @AccessorDecorator
  get name(): string {
    return this._name;
  }

  @AccessorDecorator
  set name(str: string) {
    this._name = str;
  }
}

image.png

属性装饰器(Property Decorators)

属性装饰器在属性声明之前声明.

属性装饰器的表达式将在运行时作为函数调用,并带有以下两个参数:

  1. 静态成员的类的构造函数,或者实例成员的类的原型
  2. 属性名

注意 由于属性装饰器在 TypeScript 中的初始化方式,属性描述符不作为参数提供给属性装饰器。 这是因为目前在定义原型成员时没有描述实例属性的机制,也没有办法观察或修改属性的初始值设定项。 返回值也被忽略。 因此,属性装饰器只能用于观察已为类声明了特定名称的属性。

下面的例子就是格式化日期:

import 'reflect-metadata';
import * as dayjs from 'dayjs';
const formateMetaDataKey = Symbol('format');
function format(formateString: string) {
  return Reflect.metadata(formateMetaDataKey, formateString);
}

function getFormat(target: any, propKey: string): string {
  const formateString: string = Reflect.getMetadata(
    formateMetaDataKey,
    target,
    propKey,
  );
  return dayjs(target[propKey]).format(formateString);
}

class Demo {
  @format('YYYY-MM-DD')
  date: Date;
  getDate(): string {
    const str = getFormat(this, 'date');
    return str;
  }
}

const d: Demo = new Demo();
d.date = new Date('2022-03-04 15:55:00');
console.log(d.getDate()); // 2022-03-04

参数装饰器(Parameter Decorators)

参数装饰器在参数声明之前声明.

参数装饰器的表达式将在运行时作为函数调用,并带有以下三个参数:

  1. 静态成员的类的构造函数,或者实例成员的类的原型
  2. 属性名
  3. 参数在函数参数列表的位置
function required(target: any, propKey: string, index: number) {
  console.log(target, propKey, index);
  // {} print 1
  // {} print 0
}
class Demo {
  print(@required str: string, @required suffix: string) {
    console.log(str + suffix);
  }
}

判断元素是否为空

import 'reflect-metadata';
const requiredMetadataKey = Symbol('required');

function required(target: any, propKey: string, index: number) {
  const existingRequiredParameters: number[] =
    Reflect.getOwnMetadata(requiredMetadataKey, target, propKey) || [];
  existingRequiredParameters.push(index);
  Reflect.defineMetadata(
    requiredMetadataKey,
    existingRequiredParameters,
    target,
    propKey,
  );
}

function validate(
  target: any,
  propKey: string,
  descriptor: PropertyDescriptor,
) {
  const method = descriptor.value;
  descriptor.value = function (...args: string[]) {
    const requiredParameters: number[] = Reflect.getOwnMetadata(
      requiredMetadataKey,
      target,
      propKey,
    );
    console.log(requiredParameters);
    if (requiredParameters) {
      for (const index of requiredParameters) {
        if (index >= args.length || args[index] === undefined) {
          throw new Error('参数不能为空');
        }
      }
      return method.apply(this, args);
    }
  };
}
class Demo {
  @validate
  print(xixi: string, @required str: string, @required suffix?: string) {
    console.log(xixi + str + suffix);
  }
}

new Demo().print('xixi', '12'); 

image.png