Preface πŸ‘

In TS, there is a knowledge point, decorator, which is often used by us but seldom paid attention to. In the following sections, we’ll look at class decorators, along with several other decorators in class decorators.

Here we begin the explanation of this article ~😜

πŸ˜‰ a class decorator

1. What are decorators

First, let’s talk about what a class decorator is in TypeScript.

A decorator is really a class decoration tool. For example, one day a girl might want to go out shopping, so she might go out wearing beautiful makeup. Therefore, we can regard decorators as the process of makeup, that is, a beautification process.

Now, let’s say we have a class, and then we’re going to modify it a little bit, and that’s what the decorator is going to do.

2, the characteristics of the decoration

First of all, we need to understand several characteristics of the decorator. Details are as follows:

  • The decorator itself is a function;
  • The arguments received by the decorator are constructors;
  • Decorator through@Symbol for use.

According to the above characteristics, we come to know several kinds of decorators.

3. Several kinds of decorators

(1) Order of execution

// The first decorator
function testDecorator(constructor: any) {
  console.log('decorator');
}

// The second decorator
function testDecorator1(constructor: any) {
  console.log('decorator1');
}

Decorators are executed from bottom to top and from right to left
@testDecorator
@testDecorator1
class Test {}

const test = new Test(); // decorator1 decorator
Copy the code

Decorators are executed from bottom to top and from right to left.

(2) Parameter judgment

How do we get the class decorator to accept a parameter? Take a look at this code:

// add a layer of functions
function testDecorator(flag: boolean) {
  // Factory mode
  if (flag) {
    return function (constructor: any) {
      constructor.prototype.getName = () = > {console.log('Monday');
      };
    };
  } else {
    return function (constructor: any) {};
  }
}

@testDecorator(true)
class Test {}

const test = new Test();
(test as any).getName(); // Monday
Copy the code

As we can see from the above code, we wrap a layer of functions around the class decorator, which is a bit like a Cremation, and then pass the parameters through the external function, which is the flag in the above code. The final class decorator returns a function as a result, passing parameters smoothly.

(3) Decoration standard writing method

The two decorators above belong to two simpler and less formal decorators. Here is a more standard way to write:

function testDecorator() {
  return function <T extends new (. args: any[]) = >any> (constructor: T) {
    return class extends constructor {
      name = 'Tuesday';
      getName() {
        return this.name; }}; }; }const Test = testDecorator()(
  class {
    name: string;
    constructor(name: string) {
      this.name = name; }});const test = new Test('Monday');
console.log(test.getName()); // Tuesday

Copy the code

In the code above, (… Args: any[]) => any is a function that returns the type of an object. This function takes a number of arguments, and merges them into an array, that is… The args. That

any> T can be passed through new (… Args: any[]) => any constructor, instantiated. So T can now be understood as a class or as a constructor such as constructor.

Finally, we make the Test instance accessible to the getName() method by testDecorator()() and print Tuesday.

🀐 2. Other decorators of the class

1. Method decorator

One of the things I want to emphasize here is, what do you think the execution time of a class decorator looks like? The class decorator can decorate a class as soon as the class definition is complete.

So what does a method decorator look like?

Method decorators are the same as class decorators. It will wait until the class is created, and immediately make a change to the method.

A lot of people might think, do I have to instantiate a method to make a decoration? This is not the case, as long as the class is defined, the class will help us to decorate the methods of the class. Let’s start with some code:

// Target corresponds to the prototype of the class
// Static method target corresponds to class constructor
function getNameDecorator(target: any, key: string, descriptor: PropertyDescriptor) {
  // console.log(target);
  Do some editing of properties in a method descriptor
  descriptor.writable = true;
  // You can make some changes to the original method by calling.value
  descriptor.value = function () {
    return 'decorator';
  };
}

class Test {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  @getNameDecorator
  getName() {
    return this.name; }}const test = new Test('Monday');
test.getName = () = > {
  return '123';
};
console.log(test.getName()); // decorator

Copy the code

If you look at the code above, some of you might think that the final print is 123. But in fact, because we decorated the method, the final print is a decorator.

So, once a decorator has decorated a method, you can do a lot more. You can make a lot of changes to methods, including prototype targets, key values, descriptors.

Accessor decorator

Now let’s look at the accessor decorator inside the class. Let’s start with a piece of code:

function visitDecorator(target: any, key: string, descriptor: PropertyDescriptor) {
  // console.log(123);
}

class Test {
  private _name: string;
  constructor(name: string) {
    this._name = name;
  }
  // Do not write @visitdecorator here. Writing both will raise an error
  get name() {
    return this._name;
  }
  @visitDecorator
  set name(name: string) {
    this._name = name; }}const test = new Test('Monday');
test.name = 'Tuesday';
console.log(test.name); // Tuesday

Copy the code

Where @visitDecorator is an accessor decorator. Let’s now explain the run path in the above code.

Test. Name = ‘Tuesday’; set Tuesday to name. Then, when we run console.log, we call the get method, so we’ll print Tuesday instead of Monday.

Property decorator

Let’s start with the decorator for the first property. The specific code is as follows:

function nameDecorator(target: any, key: string) :any {
  const descriptor: PropertyDescriptor = {
    writable: true};return descriptor;
}

class Test {
  @nameDecorator
  name = 'Monday';
}

const test = new Test();
test.name = 'Tuesday';
console.log(test.name); // Tuesday

Copy the code

The property decorator is also written in the form of a decorator, the @namedecorator in the above code. The decorator takes two parameters, the prototype target and the property’s name key. So here we can return a descriptor to replace the property’s original descriptor. After the substitution is complete, finally print Tuesday.


Moving on, let’s look at the second decorator. The specific code is as follows:

// The decorator cannot directly modify the property value (name) on the instance, but only the property value (name) on the prototype.
function nameDecorator(target: any, key: string) :any {
  target[key] = 'Tuesday';
}

// Name is stored on an instance of the class
class Test {
  @nameDecorator
  name = 'Monday';
}

const test = new Test();
test.name = 'Hello~';
console.log((test as any).name); // Hello~
console.log((test as any).__proto__.name); // Tuesday
Copy the code

The notable thing about this type of decorator is that the nameDecorator can only be used to modify property values on the stereotype, not directly on the instance.

4. Parameter decorator

We’ve talked about modifying methods, accessors, and properties in a class. now, we’ll look at a new kind of decorator: modifying parameters in a method in a class. Let’s start with some code:

// Prototype, method name, parameter location
function paramDecorator(target: any, key: string, paramIndex: number) :any {
  console.log(target, key, paramIndex); // Test {getInfo: [Function]}, 'getInfo', 1
}

class Test {
  getInfo(name: string, @paramDecorator age: number) {
    console.log(name, age); }}const test = new Test();
test.getInfo('Monday'.18); // Monday 18

Copy the code

As you can see, by decorating the parameters in the method, we can get the prototype of the decorator, the method name and the location of the parameters, and this is the parameter decorator.

😐 3. Small examples of the actual use of decorators

Above we have talked about many kinds of decorators related principle knowledge, now we use a practical example to guide you to use decorators better. Let’s start with some code:

const userInfo: any = undefined;

function catchError(msg: string) {
  return function (target: any, key: string, descriptor: PropertyDescriptor) {
    const fn = descriptor.value;
    descriptor.value = function () {
      try {
        fn();
      } catch (e) {
        console.log(msg); }}; }; }class Test {
  @catchError('Userinfo.name does not exist')
  getName() {
    return userInfo.name;
  }
  @catchError('Userinfo.age does not exist')
  getAge() {
    return userInfo.age;
  }
  @catchError('Userinfo.gender does not exist')
  getGender() {
    returnuserInfo.gender; }}const test = new Test();
test.getName(); // userinfo.name does not exist
test.getAge(); // userinfo.age does not exist
test.getGender(); // userinfo.gender does not exist

Copy the code

In the above code, we did a function to catch exceptions. The three methods we ended up using, getName, getAge, and getGender, were caught by encapsulating the @catchError decorator.

The above is a small practice of decoration, follow-up in-depth study can refer to some books to practice.

😏 4. Conclusion

In the previous article, we covered the most basic class decorator, as well as the four other types of decorators in class decorators. Finally, we used a small example to briefly see how the decorator works in practice.

This is the end of decorators! Do not know that friends have some new understanding of decorators?

If you think this article is helpful to you, you might as well like to support yo ~~πŸ˜‰

See you next time! πŸ‘‹ πŸ‘‹ πŸ‘‹

πŸ₯³ previous recommendation

πŸ›΅TypeScript, from 0 to Getting started takes you into the world of types