Basic understanding of decorators

  1. Decorator aim is to add new functionality to the code, with the function of the application more and more, need to give a small piece of add function, then can use a decorator, new operation method of the class, a decorator with the object-oriented inheritance ES6 type have certain difference, but in essence, they are all in the prototype operation object, Extend the functionality of the class by adding methods and properties to the prototype object prototype
  2. Decorator is to decorate the class, the class itself has a lot of functions, we can also through decorator, in addition to add new functions to the class
  3. Decorators can not only manipulate classes, but also override the methods of the class itself

Setting up

  1. To initialize a TS configuration file, and listen to the TS file compile run, initialize a decorator with TSC –init
  2. Run TS and tSC-w to compile
  3. Find the following two items in the configuration file and enable them
"experimentalDecorators": true."emitDecoratorMetadata": true.Copy the code

Decorator category

The name of the Decorator function
ClassDecorator Class decorator
MethodDecorator Method decorator
PropertyDecorator Attribute decorator
ParameterDecorator Parameter decorator

Class decorator

Class decorator

The definition of the decorator, the decorator itself, it’s a function, it’s called at run time, and the decorator class is passed as an argument to the decorator function, as a parameter.

The class decorator accepts a constructor as a parameter of type a function

const moveDecortor:ClassDecorator = (target:Function) :any= > {
  console.log(target);
}
@moveDecortor
class User {}Copy the code

After the decorator is used, define the completion decorator and write it in front of the decorator class by @ + decorator name. This means that the class below the current decorator is passed to the decorator function as an argument

{
  
  const moveDecortor:ClassDecorator = (target:Function) :any= > {
    target.prototype.forName = ():number= > {
      return 1}}@moveDecortor
  class User{} 1.You can define methods with the same name in your class to eliminate errorsconst u = new User()
  console.log((<any>u).forName());
}
Copy the code

The stack of decorators

Instead of just one decorator, you can define multiple decorators, apply them to a class, and append multiple methods and attributes to the class by superimposing decorators

{
  
  const moveDecortor:ClassDecorator = (target:Function) :any= > {
    target.prototype.forName = ():number= > {
      return 1}}const musicDecortor: ClassDecorator = (target:Function) :any= > {
    target.prototype.block = ():void= > {
      console.log('Play music'); }}@moveDecortor
  @musicDecortor
  class User{
    public forName() {}
    public block(){}}const u = new User()
  console.log((u.forName())
  u.block()
}
Copy the code

Decoration factory

The purpose of a decorator factory is to pass data to the decorator project based on the class using the decorator, and return different decorators based on the data. This function is called a decorator factory

{
  const musicDecoratorFactory = (str: string) :ClassDecorator= > {
    switch (str) {
      case 'Token':
      return (target: Function) :any= > {
        target.prototype.play = function() :void {
          console.log('Play music'+ str); }};default:
      return (target: Function) :any= > {
        target.prototype.play = function() :void {
          console.log('Play music'+ str); }}; }}@musicDecoratorFactory('Token')
  class Music {
    public play(){}}new Music().play()

}
Copy the code

Method decorator

A method decorator is used in the same way as a class decorator, but in a different way. A method decorator needs to precede a class method, passing the method as an argument to the decorator

constshowDecorator:MethodDecorator = ( ... args:any[] ):any= > {
  console.log(args)
}

class User {
  @showDecorator
  publicShow () {}} args1.If it's a static method, it's a constructor. If it's a normal method, it's a prototype.show: [Function (anonymous)] }, 
   'show'.2.The function name {3.The operation that the function can be performed, whether it can be read or written, and whether it can be copiedvalue: [Function (anonymous)], 
     writable: true.enumerable: true.configurable: true}]Copy the code

The advantage of not using arrays to receive parameters is that you don’t need to use array subscripts to find the object to operate on, and you can call it directly, which improves the code readability

const showDecorator:MethodDecorator = ( target: Object.propertyKey: string | symbol, descriptor: PropertyDescriptor ):any= > {
  console.log(args)
}

class User {
  @showDecorator
  public show () {}
}
Copy the code

Delay timer is used in decorator & decorator factory

{
  constsetTimeoutDecorator:MethodDecorator = (... args:any[]) :any= > {
    const [,,descriptor] = args;
    const msg = descriptor.value;

    descriptor.value = () = >{
      setTimeout(() = > {
        msg()
      },3000)}}class SetTime {
    
    @setTimeoutDecorator
    public times() {
      console.log('Delayed loading'); }}new SetTime().times();
}
Copy the code

The important thing to note here is that in decorators, the trick is to store the original value and then use it, making sure it uses the value of the method in the class

The global error is coarse and reasonable

  1. Decorators override error messages that can be thrown by methods using decorators, that is, word definition error messages
{
  const ErrorDecorator:MethodDecorator = (. args:any[]) = > {
    const [,,descriptor] = args;
    const method = descriptor.value
    descriptor.value = () = >{
      try {
        method()
      } catch (error) {
        console.log('This is a newly defined error.')}}}class User {
    @ErrorDecorator
    public errorMsg () {
      throw new Error('Error reported')}}new User().errorMsg()

}
Copy the code

Property decorators and method decorators

Use of property decorators

{
  const nameDecorator:PropertyDecorator = (target: Object, propertyKey: string | symbol) = > {
    Object.defineProperty(target,propertyKey, {
      set (v) {
        console.log(v); }})}class User {
    @nameDecorator
    public  str: string;
    constructor(str: string) {
      this.str = str
    }
  }
  console.log(new User('Ming'));
}
Copy the code

Property decorator parameters

[{},'str'.undefined ]
1.STR attribute name2.{} prototype chain | | constructorCopy the code

Method parameter accessor

{
  const nameDecorator:ParameterDecorator = (. args:any[]) = > {
    console.log(args);
  }
  
  class User {
    public getName(num:number.@nameDecorator str: string) {
      console.log(num,str); }}new User().getName(1.'Ming')}Copy the code

Introduction of parameters

target: Object.propertyKey: string | symbol, parameterIndex: number[{getName: [Function (anonymous)] }, 'getName'.0The first one is the current method the second one is the name of the method and the third one is where the current parameter is in the whole method parameterCopy the code

metadata

Let’s start with general data creation

let U = {
    name: 'Ordinary data'
}
Copy the code

Metadata is common data data, we can add some additional description to the name attribute, this kind of data is called metadata, TS does not have metadata by default, you need to download the relevant package file through NPM

npm init -y
npm i reflect-metadata --save
Copy the code

Using metadata

  1. Create a metadata using reflect. defineMetadata
    • Parameter 1: metadata name
    • Parameter 2: Metadata value
    • Parameter 3: The object to which the metadata information is to be set
    • Parameter 4: Which property inside the object to set metadata, property name
import 'reflect-metadata'

{
  let u = {
    name: 'Metadata'
  }

  Reflect.defineMetadata('md', {sex: 'male'},u, 'name')
  console.log(Reflect.getMetadata('md',u,'name'));
  
}
Copy the code
  1. Get meta information by using reflect. getMetadata
    • Metadata name
    • Which object, which property in the object

Metadata with parameter decorator validates parameters (to be updated)