TypeScript decorator definition

TypeScript decorator definition

Preface:

Decorator has been proposed in ECMAScript , but it has not yet been finalized; it has been implemented in TypeScript, but it is still an experimental feature. If you want to use the decorator, you need to set experimentalDecorators property in tsconfig.json to true.

1. Concept

1.1 Definition

A decorator is a new type of declaration that can be applied to class declarations, methods, accessors, properties, and parameters. The decorator is used with the @ symbol plus a function name, such as @testDecorator , where testDecorator must be a function or return a function. This function is called at runtime and the decorated statement is automatically passed in as a parameter.

It is worth noting that the decorator should be placed immediately before the content to be modified, and all decorators cannot be used in declaration files.d.ts. and any external context (such as declare ).

The definition and use of the decorator are as follows:

// Define a function as a decorator function to use function testDecorator() {}

//Use the decorator @testDecorator via the @ symbol

1.2 Decorator Factory

The so-called decorator factory is also a function. The difference from the ordinary decorator function is that its return value is a function, and the returned function is used as the function called by the decorator. If you use a decorator factory, you can pass different parameters according to the current usage when using it, but when using it, you need to add a function call.

The sample code is as follows:

// Decorator factory, the return value is a function function testDecorator() {
    return function() {}
}

//Use the decorator @testDecorator() by @symbol + function call

1.3 Decorator Combination

Decorators can be used in combination, that is, you can use one target and reference multiple decorators.

The sample code is as follows:

// Define two decorator functions function setName() {}
function setAge() {}

//Use decorator @setName
@setAge
class Person {}

If multiple decorators are used, the decorators are executed in order, and the execution order is as follows:

If a normal decorator function is used, the execution order is from bottom to top.

The sample code is as follows:

function setName(constructor: any) {
  console.log('setName', constructor)
}
function setAge(constructor: any) {
  console.log('setAge', constructor)
}
@setName
@setAge
class Person {}
/* The execution results are as follows:
setAge [Function: Person]
setName [Function: Person]
*/


If it is a decorator factory, its execution order is to execute the factory function from top to bottom, and then execute the function returned by the factory function from bottom to top. The sample code is as follows

function setName() {
  console.log('get setName')
  return function (constructor: any) {
    console.log('setName', constructor)
  }
}
function setAge() {
  console.log('get setAge')
  return function (constructor: any) {
    console.log('setAge', constructor)
  }
}
@setName()
@setAge()
class Person {}
/* The execution results are as follows:
get setName
get setAge
setAge [Function: Person]
setName [Function: Person]
*/

1.4 Decorator Evaluation

Decorators on different declarations in a class definition are applied in the order specified below:

  • A parameter decorator, method decorator, accessor decorator or property decorator is applied to each instance member;
  • A parameter decorator, method decorator, accessor decorator or property decorator is applied to each static member;
  • The parameter decorator is applied to the constructor;
  • Class decorators are applied to classes.

2. Class Decorator

Class decorators are used before class declarations and must be placed immediately before the content to be decorated. Class decorators are applied to class declarations.

The class decorator expression will be called as a function at runtime, and it has one parameter, which is the constructor of this class.

The sample code is as follows:

let sign = null
function setName() {
  return function (constructor: Function) {
    sign = constructor
  }
}
@setName()
class Info {
  constructor() {}
}
console.log(sign === Info) // true
console.log(sign === Info.prototype.constructor) // true

From the above code, we can see that constructor property of the prototype object of class Info actually points to Info itself.

We can also use decorators to modify the prototype object and constructor of a class. The sample code is as follows:

// * Modify the prototype object and constructor function addName(constructor: { new (): any }) {
  constructor.prototype.name = 'A bowl of Zhou'
}
@addName
class Person {}
const person = new Person()
console.log(person.name) // error Property "name" does not exist on type "A"

In the above code, we use the addName modifier to add a name attribute to the prototype of the Person class, so that objects instantiated by the Person class can access the name attribute. However, this is not actually the case. An exception has been thrown here. To solve this problem, you can use type assertions or define an interface with the same name and declare it through merging.

The sample code is as follows:

function addName(constructor: { new (): any }) {
  constructor.prototype.name = 'A bowl of Zhou'
}
@addName
class Person {}
const person = new Person()
// 1. Type assertion // console.log((person as any).name) // 一碗周 // 2. Define an interface with the same name and declare a merge interface Person {
  name: string
}

console.log(person.name) // Yiwan Zhou

And we can also overload the constructor through the decorator, the sample code is as follows:

// * Overload the constructor function classDecorator<T extends { new (...args: any[]): {} }>(
  constructor: T,
) {
  return class extends constructor {
    name = 'A bowl of Zhou'
    hobby = 'coding'
  }
}
@classDecorator
class Person {
  age = 18
  name: string
  constructor(name: string) {
    this.name = name
  }
}
const person = new Person('Yiwan Zhou')
console.log(person)
/* The execution results are as follows:
{
  age: 18,
  name: 'A bowl of Zhou',
  hobby: 'coding',
}
*/

We can also pass parameters through the decorator factory. The sample code is as follows:

// Define a decorator factory function classDecorator(_name: string) {
  return function <T extends { new (...args: any[]): {} }>(constructor: T) {
    return class extends constructor {
      name = _name
      hobby = 'coding'
    }
  }
}
@classDecorator('A bowl of Zhou')
class Person {
  age = 18
  name: string
  constructor(name: string) {
    this.name = name
  }
}
const person = new Person('a bowl of porridge')
console.log(person)
/* The execution results are as follows:
{
  age: 18,
  name: 'A bowl of Zhou',
  hobby: 'coding',
}
*/

3. Method Decorator

The method decorator is used to process the methods in the class. It can process the property descriptor of the method (for what is the property descriptor, please refer to Object.defineProperty()) and the method definition. The method decorator is also called as a function at runtime, which contains three parameters.

The details are as follows:

For static members, it is the class constructor; for instance members, it is the class prototype object.

The member's first name.

The property descriptor of the member.

Note that if the code output targets a version less than ES5, the property descriptor will be undefined .

The following code defines a simple method decorator through the decorator factory. The sample code is as follows:

// Decorator factory function enumerable(bool: boolean) {
  /**
   * The method decorator accepts three parameters:
   * 1. target: for static members, it is the constructor of the class; for instance members, it is the prototype object of the class* 2. propertyName: the name of the member* 3. descriptor: property descriptor, its type is PropertyDescriptor
   */
  return function (
    target: any,
    propertyName: string,
    descriptor: PropertyDescriptor,
  ) {
    //Determine whether the method is enumerable based on the passed bool descriptor.enumerable = bool
  }
}
class Info {
  constructor(public name: string) {}
  @enumerable(false)
  getName() {
    return this.name
  }
}
const info = new Info('A bowl of Zhou')
// If printed directly, the object does not contain the getName() method because the method is not enumerable.
console.log(info) // { name: 'Yiwan Zhou' }
// But you can call this method console.log(info.getName()) // Yiwan Zhou

In the above code, we directly modified the property descriptor of the method in the class through the decorator.

If the method decorator returns a value, then this value will be used as the property descriptor object of the method. The sample code is as follows:

// Decorator factory function enumerable(bool: boolean) {
  return function (
    target: any,
    propertyName: string,
    descriptor: PropertyDescriptor,
  ) {
    return {
      value: function () {
        return 'Error: name is undefined'
      },
      enumerable: bool,
    }
  }
}
class Info {
  constructor(public name: string) {}
  @enumerable(false)
  getName() {
    return this.name
  }
}
const info = new Info('A bowl of Zhou')
console.log(info) // { name: 'Yiwan Zhou' }
console.log(info.getName()) // Error: name is undefined

In the above code, our method decorator returns an object whose value property modifies the method definition, so the final result is Error: name is undefined .

4. Accessor Decorator

The accessor decorator is set and get methods we learned before, one is triggered when setting the property value, and the other is triggered when getting the property value.

The accessor decorator also accepts three parameters, just like the method decorator, so I won’t go into details here.

The sample code is as follows:

function enumerable(bool: boolean) {
  return function (
    target: any,
    propertyName: string,
    descriptor: PropertyDescriptor,
  ) {
    descriptor.enumerable = bool
  }
}
class Info {
  private_name: string
  constructor(name: string) {
    this._name = name
  }
  @enumerable(false)
  get name() {
    return this._name
  }
  set name(name) {
    this._name = name
  }
}

It is worth noting that in TypeScript it is not allowed to decorate both get and set accessors of a member.

5. Property Decorator

The property decorator is declared before the property declaration and it has two parameters as shown below:

  • For static members, it is the class constructor; for instance members, it is the class prototype object.
  • The member's first name.

The sample code is as follows:

function printPropertyName(target: any, propertyName: string) {
  console.log(propertyName)
}
class Info {
  @printPropertyName
  name: string
  @printPropertyName
  age: number
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
}
new Info('Yiwan Zhou', 18)

The execution results are as follows:

name
age

6. Parameter Decorator

The parameter decorator has three parameters, as follows:

  • For static members, it is the class constructor; for instance members, it is the class prototype object.
  • The member's first name.
  • The index of the parameter in the function's parameter list.

The function of the parameter decorator is to monitor whether a method parameter is passed in. The return value of the parameter decorator will be ignored.

The sample code is as follows:

function required(target: any, propertyName: string, index: number) {
  console.log(`The modified parameter is the ${index + 1}th parameter of ${propertyName}`)
}
class Info {
  name: string = 'A bowl of Zhou'
  age: number = 18
  getInfo(prefix: string, @required infoType: string): any {
    return prefix + ' ' + this[infoType]
  }
}
interface Info {
  [key: string]: string | number | Function
}
const info = new Info()
info.getInfo('', 'age') // Modifies the second parameter of getInfo

Here we use the parameter decorator before the second parameter of getInfo method so that we can get some information in the decorator.

This is the end of this article about TypeScript decorator definitions. For more information about TypeScript decorators, please search 123WORDPRESS.COM’s previous articles or continue to browse the following related articles. I hope you will support 123WORDPRESS.COM in the future!

You may also be interested in:
  • A brief understanding of how to inherit the Error class in TypeScript
  • Detailed explanation of JavaScript private class fields and TypeScript private modifiers
  • TypeScript interface definition case tutorial
  • TypeScript function definition and use case tutorial
  • TypeScript learning notes: TypeScript class definition, class inheritance, class member modifiers

<<:  MySQL time types and modes details

>>:  Implementation of CSS equal division of parent container (perfect thirds)

Recommend

JavaScript mobile H5 image generation solution explanation

Now there are many WeChat public account operatio...

Deleting two images with the same id in docker

When I created a Docker container today, I accide...

Introduction to the use and difference between in and exists in MySQL

First put a piece of code for(int i=0;i<1000;i...

How to make form input and other text boxes read-only and non-editable in HTML

Sometimes, we want the text boxes in the form to b...

Example code for implementing a simple search engine with MySQL

Table of contents Preface Introduction ngram full...

Implementation of multiple instances of tomcat on a single machine

1. Introduction First of all, we need to answer a...

Linux uses stty to display and modify terminal line settings

Sttty is a common command for changing and printi...

A few experiences in self-cultivation of artists

As the company's influence grows and its prod...

jQuery plugin to implement minesweeper game (1)

This article shares the specific code of the firs...

Div nested html without iframe

Recently, when doing homework, I needed to nest a ...

Detailed explanation of Linux text editor Vim

Vim is a powerful full-screen text editor and the...

XHTML Getting Started Tutorial: Form Tags

<br />Forms are an important channel for use...