Decorator this noun, if not write presents other front end, nest is thought to be not very familiar with classmates, in simple terms, a decorator is function, provides a specific function, used to describe the classes, methods, properties, parameters, to add more powerful function, at the same time to decouple the original logic, as a kind of aop programming implementation.

Or maybe a little on a regular basis

For example, @connect was used when redux was used in React

@connect(mapStateToProps, mapDispatchToProps)
class Index extends React.Component{}
Copy the code

The VUE-property-decorator plug-in was used in VUe2

@Component({})
class Index extends Vue {}
Copy the code

Today, I’m going to show you what a decorator really is.

Decorator proposal in javascript

There is a javascript decorator proposal, but it has been slow to land.

In the early days, decorators were proposed differently, and Typescript implemented decorators early, just as Angular2 fully used Typescript for refactoring, using a lot of decorators that were proposals at the time.

The Angular team, and later the Nest team, certainly didn’t approve of the new decorator proposal, given that the decorator proposal was already completely different. Just like the Promise A+ at that time, it was also promoted by the community, but before the official Promise was realized, the community had already used the Promise library. In order to be compatible with these libraries, the judgment of Promise is now based on thenable, the duck type. Rather than using instanceof, which is more accurate, to judge from the bottom.

Unlike promises, promises have a double inversion of control. They focus on the order in which they are implemented, and how they are implemented doesn’t really matter as much as how they are used. Decorators, on the other hand, are really code logic, and changing one rule means the whole logic could be completely different. The angular team and the Nest team, already the Typescript team, will certainly be reluctant to approve the proposal at TC39. This led to the continued shelved of the decorator proposal.

But it’s funny, because normally, when a proposal is rejected, you have to go back to stage1 and start all over again, but the decorator stays on stage2.

Use decorators

Until the proposal is officially implemented, we use decorators, usually in one of two ways

  • usebabelThe plug-in
  • useTypescript

As mentioned earlier, the Typescript team implemented decorators a long time ago, so all we need to do is create a.ts file and be free to use the decorator with the experimentalDecorators option on, of course.

Decoration factory

Before we introduce decorators, let’s briefly introduce a concept — the decorator factory.

As the name suggests, a factory is a place where you assemble things, and a decorator factory is a place where you assemble values and things to decorate.

Compared to a normal decorator function, it has an extra layer of calls to pass the data to be assembled, so the biggest difference between a decorator factory and a normal decorator is its custom parameters.

Class decorator

Class decorator, declared above the class keyword.

Simply put, you pass in this class as a decorator argument, and inside the decorator function, you can do all sorts of things to this class.

Without further ado, let’s get right to the code

@Init
class Index {
  public age = 12
}

function Init<T extends {new(... args:any[]) : {}} > (constructor: T) {
  return class extends constructor {
    age = 21}}console.log(new Index())

// class_1 { age: 21 }

// function Init
      
        {}>(constructor: T): {
      
//  new (...args: any[]): (Anonymous class);
// prototype: Init
      
       .(Anonymous class);
      
// } & T
Copy the code

When we instantiate the Index class, we call its decorator Init and pass in the Index. From there, we can use this function to do anything with the class.

Now let’s look at the decorator factory for class decorators. How do we use the @connect method

@InjectSex('male')
class Two {}

function InjectSex(sex: 'male' | 'woman') {
  return function<T extends {new(... args:any): {}}>(target: T) {
    target.prototype.sex = sex
    return target
  }
}

console.log(Reflect.getPrototypeOf(new Two()))

// {sex: 'male'}
Copy the code

Method decorator

A method decorator is used to decorate a method. Unlike a class decorator, which has only one target parameter, a method decorator takes three parameters

  • targetThe class instance
  • keyMethod name
  • descriptorThe descriptor used to describe the method, i.eObject.definePropertyIn the third argument to the methodvalue,writable,enummerable,configurable
class Fun {
  @AddOne
  log(x: number) {
    console.log(x)
  }
}

function AddOne(target, key, descriptor) {
  console.log(target, 'target') // { log: [Function (anonymous)] } target
  console.log(key, 'key') // log key
  console.log(descriptor, 'descriptor')
  / / {
  // value: [Function (anonymous)],
  // writable: true,
  // enumerable: true,
  // configurable: true
	// } descriptor
  
  const val = descriptor.value
  descriptor.value = function(. args) {
    return val(args[0] + 1)}return descriptor
}

const fun = new Fun
fun.log(1)

/ / 2
Copy the code

We hijack the old method by using the value property in descriptor, and rewrite it so that we can modify an existing method with minimal cuts.

In the case of a decorator factory, we still need to wrap functions around it

class FuncTwo {
  @InjectPrefix('Tony')
  log(x) {
    console.log(x)
  }
}

function InjectPrefix(prefix: string) {
  return function(target, key, descriptor) {
    const val = descriptor.value
    descriptor.value = function(. args) {
      return val(prefix + args[0])}return descriptor
  }
}

const funcTwo = new FuncTwo
funcTwo.log('Stark')

// Tony Stark
Copy the code

Attribute decorator

A property decorator is commonly used for property hijacking. It takes in two parameters, the target and the name of the current property. We can add values to the decorated property through the decorator factory.

class Prop {
  @init(16)
  age: number
}

function init(age: number) {
  return function(target, key) {
    target[key] = age
    return target
  }
}

const prop = new Prop
console.log(prop.age)

/ / 16
Copy the code

Parameter decorator

The parameter decorator accepts three parameters: target, key(the current method), and index(the subscript of the current parameter)

class Param {
  log(@require name: string.@require age: number) {
    console.log(name, age)
  }
}

function require(target, key, index) {
  console.log(target, key, index)
  return target
}

const param = new Param
param.log('Joe'.18)

// { log: [Function (anonymous)] } log 1
// { log: [Function (anonymous)] } log 0
/ / zhang SAN 18
Copy the code

However, it is common to use method decorators to accompany its use, as in the following example

class Param {
  @Validate
  log(@requirename? :string.@requireage? :number) {
    console.log(name, age)
  }
}

function Validate(target, key, descriptor) {
  const val = descriptor.value
  const required = val.required
  console.log(required) / / [0, 1]
  descriptor.value = function(. args) {
    required.forEach(index= > {
      if(! args[index]) {throw new Error('Missing arguments')}})returnval(... args) }return descriptor
}

function require(target, key, index) {
  target[key].required = [index, ...(target[key].required || [])]
  return target
}

const param = new Param
param.log()

// /Users/asarua/Desktop/demo/decorator/params-decorator.ts:13
// required.forEach(index => {
             ^
// Error: parameter missing
Copy the code

Add the required parameter to the target[key] method using the require parameter decorator and Validate with Validate.

conclusion

Decorators are things that Java engineers use all the time, adding a bunch of them to each Controller, Service, and method.

In fact, front-end engineers can also try to use it in daily work, in some methods that need to perform log what, using decorators, can better decouple irrelevant logic, better maintenance.