preface

I used Nest to write a simple background system before, but I didn’t use TS much, and I only had a vague understanding of the large number of decorators used in it, and only stayed at the level that I could use. After this internship, I went back to school and began to make plans for the graduation project. I planned to use Nest to write the background, so now I have time to come back and comb the concept of decoration again. Whether Nest is used or not, I think decorators are one of the things that will have to be learned, as they are currently Stage2 in JS (draft stage, which provides an initial draft specification that doesn’t differ much from the features included in the final standard). After the draft, only incremental amendments are accepted in principle. Start experimenting with implementations in the form of Polyfill, implementation engines (which provide native support for draft execution), or compiled transformations (such as Babel), which are supported as experimental features in TS, so this is a future direction for JS. There is also the TS official description of the decorator.

Decorator concept

It is a special type of declaration that can be attached to a class declaration, method, attribute, or parameter to modify the behavior of the class. In layman’s terms, a decorator is a function/method that can be injected to extend the functionality of a class, method, or property parameter. Common decorators are:

  • Class decorator
  • Attribute decorator
  • Method decorator
  • Parameter decorator

Decorator written: ordinary decorator (can not be sent), decorator factory (can be sent)

Class decorator

The class decorator is declared < next to the class declaration) before the class declaration. Class decorators are applied to class constructors and can be used to monitor, modify, or replace class definitions. Passing in a parameter

Common decorator: cannot pass parameters

The target property is the class that uses the decorator */
function logClass(target: any) {
  target.prototype.apiUrl = 'http://www.baidu.com'
  target.prototype.hello = () = > {
    console.log("hello world")}}@logClass
class HttpClient {
  constructor(){}}const http: any = new HttpClient()
​
console.log(http.apiUrl) // http://www.baidu.com
http.hello() //hello world
Copy the code

Decorator factory: Parameters can be passed

/** * decorator factory * params is the argument we pass * Target is the class that uses the decorator */
function logClass(params: string) {
  return function (target: any) {
    target.prototype.hello = () = > {
      console.log(params)
    }
  }
}
​
@logClass('hello world')
class HttpClient {
  constructor(){}}const http: any = new HttpClient()
http.hello()  // Print hello world
Copy the code

Overloaded constructor

A class decorator expression is called at run time as a function, with the constructor of the class as its only argument. If the class decorator returns a value, it replaces the class declaration with the provided constructor

function logClass(target: any) {
  return class extends target {
    apiUrl: string = 'Modified apiUrl'
    getData() {
      console.log('changes:.this.apiUrl)
    }
  }
}
​
@logClass
class HttpClient {
  public apiUrl: string | undefined
  constructor() {
    this.apiUrl = 'Unmodified apiUrl'
  }
​
  getData() {
    console.log(this.apiUrl)
  }
}
​
const http = new HttpClient()
http.getData() // Modify: Modified apiUrl
Copy the code

Attribute decorator

The property decorator expression is called as a function at run time, passing in the following two arguments:

  • Examples of decoration. Constructor of the class for static members and prototype object for instance members
  • The attribute name of the decoration
/** * attribute decorator * params is the argument passed to the decorator * target is the instance of the decorator * attr is the attribute of the decorator */
function logProperty(params: any) {
  return function (target: any, attr: string) {
    // In this way, property values can be modified through decorators
    target[attr] = params
  }
}
​
class HttpClient {
  @logProperty('Property decorator assignment')
  public apiUrl: string | undefined
  constructor(){}getData() {
    console.log(this.apiUrl)
  }
}
​
const http = new HttpClient()
http.getData() // Attribute decorator assignment
Copy the code

Method decorator

It is applied to the method’s property descriptor and can be used to monitor, modify, or replace the method definition. The method decorator passes the following parameters at runtime:

  • Examples of decoration. Constructor of the class for static members and prototype object for instance members
  • Name of member
  • Attribute descriptor for a member
/** * the value params passes to the decorator * the instance of the target decorator * methodName methodName * descriptor */
function get(params: any) {
  console.log(params) // http://www.baidu.com
  return function (target: any, methodName: string, descriptor: PropertyDescriptor) {
    console.log(target)
    console.log(methodName)
    console.log(descriptor)
    // Save the original passed method before modifying it
    let originalMethod = descriptor.value
​
    // Override the passed method
    descriptor.value = function (. args:any[]) {
      // Execute the original method
      originalMethod.apply(this, args)
​
      args = args.map(val= > +val)
​
      console.log(args)
    }
  }
}
​
class HttpClient {
  constructor(){}@get('http://www.baidu.com')
  getApi(){}}const http: any = new HttpClient()
​
http.getApi('123'.'456'.'789')  // Print [123, 456, 789]
Copy the code

Method parameter decorator

We can use the parameter decorator to add some element data to the prototype of the class, passing in three parameters:

  • Examples of decoration. Constructor of the class for static members and prototype object for instance members
  • The method name
  • The index of a parameter in the function argument list
function logParams(param: any) {
  return function (target: any, methodName: string, paramIndex: number) {
    console.log(target)     / / httpClient instance
    console.log(methodName) // getApi
    console.log(paramIndex) / / 0}}class HttpClient {
  constructor(){}getApi(@logParams('id') id: number) {
    console.log(id)
  }
}
​
const http = new HttpClient()
​
http.getApi(123456)
Copy the code

Decorator execution order

Here is the conclusion, the specific code please see below:

  • Properties > Methods > Method Parameters > Classes
  • If there are multiple decorators of the same type, it will execute the following one first (bottom to top, method parameter decorators executed from right to left)
// Start with some decorator definitions
function logClass1(target: any) {
  console.log('logClass1')}function logClass2(target: any) {
  console.log('logClass2')}function logAttribute1(param? :any) {
  return function (target: any, attrName: string) {
    console.log('attribute1')}}function logAttribute2(param? :any) {
  return function (target: any, attrName: string) {
    console.log('attribute2')}}function logMethod1(param? :any) {
  return function (target: any, methodName: string, descriptor: PropertyDescriptor) {
    console.log('logMethod1')}}function logMethod2(param? :any) {
  return function (target: any, methodName: string, descriptor: PropertyDescriptor) {
    console.log('logMethod2')}}function logParam1(param? :any) {
  return function (target: any, methodName: string, index: number) {
    console.log('logParam1')}}function logParam2(param? :any) {
  return function (target: any, methodName: string, index: number) {
    console.log('logParam2')}}@logClass1
@logClass2
class HttpClient {
  @logAttribute1(a)api1: string | undefined
  @logAttribute2(a)api2: string | undefined
​
  constructor(){}@logMethod1(a)get1(){}@logMethod2(a)get2(){}get3(@logParam1() param1: string.@logParam2() param2: string){}}Copy the code

The final print of the above code is as follows, which verifies our initial conclusion about the execution order

attribute1
attribute2
logMethod1
logMethod2
logParam2
logParam1
logClass2
logClass1
Copy the code

conclusion

Decorators allow you to add new functionality to an existing object without changing its structure. This pattern creates a decorator class that wraps the original class and provides additional functionality that improves code reusability and reduces code volume while preserving the integrity of the class method signature.