Understanding of decorators in TypeScript

Keywords: Javascript TypeScript

definition

Decorators are special types of declarations that can be attached to class declarations, methods, accessors, properties, or parameters

It is a function of dynamically extending objects without changing the original class and using inheritance

Similarly, in essence, it is not a tall structure, but an ordinary function. The form of @ expression is actually the syntax of Object.defineProperty

expression must also be a function after evaluation. It will be called at runtime and the decorated declaration information will be passed in as parameters

Mode of use

Since typescript is an experimental feature, to use it, you need to start it in the tsconfig.json file, as follows:

{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}

The use of typescript decorator is basically the same as that of javascript

The decorator of class can decorate:

class

Method / attribute

parameter

Accessor

Class decoration

For example, declare a function addAge to add age to the attribute age of Class

function addAge(constructor: Function) {
  constructor.prototype.age = 18;
}

@addAge
class Person{
  name: string;
  age!: number;
  constructor() {
    this.name = 'huihui';
  }
}

let person = new Person();

console.log(person.age); // 18

The above code is actually equivalent to the following form:

Person = addAge(function Person() { ... });

As can be seen from the above, when the decorator is used as a modifier class, the constructor will be passed in. constructor.prototype.age is to add an age attribute to each instantiated object

Method / attribute decoration

Similarly, the decorator can be used to decorate the method of the class. At this time, the parameters received by the decorator function become:

target: the prototype of the object

propertyKey: name of the method

Descriptor: Property descriptor of the method

You can see that these three properties are actually the three parameters of Object.defineProperty. If they are class properties, the third parameter is not passed

Examples are as follows:
//Declare decorator modifier methods / properties

function method(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  console.log(target);
  console.log("prop " + propertyKey);
  console.log("desc " + JSON.stringify(descriptor) + "\n\n");
  descriptor.writable = false;
};

function property(target: any, propertyKey: string) {
  console.log("target", target)
  console.log("propertyKey", propertyKey)
}

class Person{
 @property
 name: string;
 constructor() {
   this.name = 'huihui';
 }

 @method
 say(){
   return 'instance method';
 }

 @method
 static run(){
   return 'static method';
 }
}

const xmz = new Person();

// Modify instance method say
xmz.say = function() {
 return 'edit'
}

The output is shown in the following figure:

Parameter decoration

Receive three parameters, namely:

target: the prototype of the current object

propertyKey: name of the parameter

index: position in parameter array

function logParameter(target: Object, propertyName: string, index: number) {
  console.log(target);
  console.log(propertyName);
  console.log(index);
}

class Employee {
  greet(@logParameter message: string): string {
      return `hello ${message}`;
  }
}
const emp = new Employee();
emp.greet('hello');

The input is shown in the figure below:

Accessor decoration

The use method is consistent with the decoration method, as follows:

function modification(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  console.log(target);
  console.log("prop " + propertyKey);
  console.log("desc " + JSON.stringify(descriptor) + "\n\n");
};

class Person{
 _name: string;
 constructor() {
   this._name = 'huihui';
 }

 @modification
 get name() {
   return this._name
 }
}

Decorator factory

If you want to pass parameters to turn the decorator into a factory like function, you only need another function inside the decorator function, as follows:

function addAge(age: number) {
  return function(constructor: Function) {
    constructor.prototype.age = age
  }
}

@addAge(10)
class Person{
  name: string;
  age!: number;
  constructor() {
    this.name = 'huihui';
  }
}

let person = new Person();

Execution sequence

When multiple decorators are applied to a declaration, the decorator expression will be evaluated from top to bottom, and the evaluation result will be called from bottom to top as a function, for example:

function f() {
    console.log("f(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("f(): called");
    }
}

function g() {
    console.log("g(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("g(): called");
    }
}

class C {
    @f()
    @g()
    method() {}
}

// output
f(): evaluated
g(): evaluated
g(): called
f(): called

Application scenario

It can be seen that there are two significant advantages of using decorators:

The code becomes more readable, and the decorator name is equivalent to a comment

Extend the original function without changing the original code

In the following usage scenarios, with the help of the characteristics of the decorator, in addition to improving readability, for existing classes, the original functions can be extended without changing the original code through the characteristics of the decorator

Posted by Sphynx on Sat, 20 Nov 2021 23:53:47 -0800