This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.

This series is designed to help you open TypeScript the right way.

preface

Class is not specific to TS, but is a new template for creating objects in ES6. But actually we don’t use it much when we write JS. This is because JS is object-based and class is implemented on top of prototypes. So unless you’re already used to OOP, class is probably just a JS writing style for you.

So why are we talking about class here? On the one hand, some programmers will appreciate the object-oriented style of programming, and the use of class does bring many benefits. Decorators in TS, on the other hand, are class-based, and the use of decorators can be very convenient.

And our purpose today is to focus on the second point. Decorators are used in many mature TS frames. To understand how decorators are used, let’s talk about classes first. This is a bit “for this vinegar, I just pack this dish of dumplings” taste 🤣.

series

A more practical guide to TypeScript: Getting Started with Ink-Digging (juejin. Cn)

A more practical TypeScript guide: From ES6 classes to TS Decorators – Nuggets (juejin. Cn)

Class (class)

Tips: If you already know about classes, you can skip to the decorator section

Next, we’ll give you a quick and brief introduction to the class through an example (see the link in the title for more).

A simple class and use

Let’s start by creating a class that implements a common function (such as setting text)

Constructor (STR :string) {this.text = STR} tellText (): constructor (STR :string) {this.text = STR} telltext(): Void {console.log(this.text)}} let setText = new setText (' ABC ') // Create an instance of class (actually an object) setText.tellText () Telltext >> 'ABC'Copy the code

The process of creating an instance is simply to create an empty object, bind the class’s members to the object’s properties, and bind the class’s methods to the object’s __proto__.

Class inheritance and supper

Next, we let a derived class inherit from the base class and override the parent constructor to implement the text merge method

class JoinText extends SetText { textCom:string constructor(str1:string, Str2 :string) {super(str1) // The constructor of the derived class must have a super call this.textcom = str1 + str2} telltext():void { console.log(this.textCom) } } let setText = new JoinText('abc','efg') console.log(setText) >> JoinText { text: 'abc', textCom: 'abcefg' } setText.telltext() >> 'abcefg'Copy the code

The super call executes the constructor of the base class in order for the derived class to inherit the object of the base class. So super is necessary

Getter and setter

We create two special ‘methods’ to get or set some values of the object

class JoinText extends SetText {
    textCom:string
    constructor(str1:string, str2:string) {
        super(str1)
        this.textCom = str1 + str2
    }
    telltext():void {
        console.log(this.textCom)
    }
    get getStr1():string {
        return `${this.text}`
    }
    set setStr1(str2:string) {
        this.text = str2
    }
}
let setText = new JoinText('abc','efg')
setText.setStr1 = 'xxx'
console.log(setText.getStr1)
>> 'xxx'
Copy the code

The purpose of this assignment is to understand setter and getter usage, although the effect is the same using settextt.text directly. Importantly, when you call GET and set, you don’t call methods, you call them as properties

static

Finally, we add a static member, which can be called either directly by the class or by static methods.

class JoinText extends SetText {
    static from:string = 'SetText'  
    textCom:string
    constructor(str1:string, str2:string) {
        super(str1)
        this.textCom = str1 + str2
    }
    telltext():void {
        console.log(this.textCom)
    }
}
console.log(JoinText.from)
>> 'SetText'
Copy the code

Now that we have a quick look at the use of classes, let’s move on to the main issue: using decorators

A decorator

In a nutshell: Decorators are used to add and set various things in various parts of a class.

Decorators are divided into class decorators, member decorators, method decorators and parameter decorators. Before a class, before a class member, before a method, and before a method parameter.

Let’s start with an example (a controller for NestJS) (Nest is an Express-based typescript server framework)

@controller ('cats') // Class decorator export class CatsController {@get ('/hello') @httpCode (200)// Method decorator recall(@req ()/* Parameter decorator */ Request: request) : string {console.log(request) // Will output the requested content return 'hello nestjs'}}Copy the code

It doesn’t matter if you don’t understand, let’s explain each sentence step by step:

First @Controller(‘cats’), which is a class decorator that comes before the class, defines a basic Controller that adds a route. That is, the class is now a controller with a /cats route.

@get (‘/hello’) and @httpCode (200) are method decorators that precede the methods of the class. They add more functionality to the class by adding a /hello child route and a status code that returns 200, respectively

@req () is an argument decorator that precedes the argument of a function in the class, and in this case, makes it the request argument.

The result of the final run looks like this:

As you can see, this class really only has one method before adding these decorators:

class CatsController {
    recall(request: Request):string {
        console.log(request)
        return 'hello nestjs'
    }
}
Copy the code

These decorators, in turn, add a lot of functionality to the class, acting as a controller that handles incoming requests and returns responses to the client. This is the use of decorators in real development, through a high degree of encapsulation, more quickly to achieve the function. It also has OOP(object-oriented development) characteristics.

The basic use

So how does decorator do all of this?

First, we learn how to use a class by implementing a class with hints

Initialization:

class Toast{
    type:string
    tell(text:string):void {   
    }
}
Copy the code

Class decorator

Now this class does nothing. Let’s start by adding a class decorator that has a tiltle

Const Description: ClassDecorator = (target) = > {/ / note 1 target. The prototype. The title = 'I am a Toast / / 2} @ the Description Type :string tell(text:string):void {}} let Toast = new Toast() interface NewToast extends // Note 3 console.log((Toast as newtoast.title) >> 'I am a Toast'Copy the code
  • Note 1: We define a Description decorator whose type isClassDecorator(Type decorator). Pay attention to,Decorators are a class of functions.Different decorators receive different parametersHere,The target argument received by the class decorator is the class itself that it decorates. In the class decorator, we manipulate the class by adding attributes to its prototype.
  • Note 2:Target.prototype. title = 'I am a Toast'Manipulating the prototype is like adding one to the classTitle = 'I am a Toast'If you don’t know much about the prototype, you can comment in the comments section. I will publish a separate article about the prototype
  • Note 3: Here we define an interface that inherits Toast and adds the title property. The purpose is to let ts know that toast has a title through assertions. Otherwise, an error will be reported because the class does not have a title member. (Alternatively, you can simply use toast[‘title’], which will not be checked)

With the class decorator, we give a class a title. But now that the title is fixed, if we need to pass in a parameter to set the title, we can use the decorator factory to do so.

Decoration factory

A decorator factory is a function on a variety of decorators to receive parameters 🤣.

For example, we changed the class decorator above to a decorator factory:

Const Description = (title:string):ClassDecorator => {return (target) => {target.prototype.title = title}} // Note 2 Class Toast{type:string tell(text:string):void {}} let Toast = new Toast( NewToast extends Toast{title:string} console.log((Toast as newtoast.title) >> 'decorator duck decorator 📢'Copy the code
  • Note 1: The decorator factory wraps the class decorator in a function and returns it. So the return from this function is of class decorator type. It is used like a normal function, but returns a class decorator. It’s not that mysterious
  • Note 2: Since the decorator factory is a function call that returns the decorator, here we call it directly and write the arguments.

With the class decorator, we have the ability to customize the title of the class

Member decorator

Next, we add a member decorator that decorates the type of the class to indicate that the warning function is a warning.

const initType:PropertyDecorator = (target, PropertyKey) => {// Note 1 Target [propertyKey] = 'Warning' // Note 2} @description (' decorator duck decorator 📢') class Toast{@initType// Note 3 type:string tell(text:string):void { } } let toast = new Toast() console.log(toast.type) >> 'warning'Copy the code
  • Note 1: A member decorator is also a function that takes the current class (target) and the name of the member it decorates (propertyKey).
  • Note 2: This is equivalent totoast.type = warning
  • Note 3: Member decorator decorates which member puts which member on which member

You can also use a decorator factory for a custom type here, except that the returned type is the method decorator. Writing is similar, interested in children’s shoes can try.

Method decorator

Finally, we decorate the method with a method decorator that prints out the prompt:

Const initTell:MethodDecorator = (Target, propertyKey, Descriptor) => {// Note 1 (descriptor. Value as unknown) = (text: // Note 2 console.log(' Warning! } descriptor. Writable = false // Target [propertyKey] = (text: descriptor) String):void => {// console.log(' Warning! Here's the writing problem: ',text) //}} @description (' decorator! Decorator 📢') class Toast{@initType type:string @inittell tell(text:string):void {}} let Toast = new toast () toast.tell(' button ') >>'warning! Here's the write problem: button 'Copy the code
  • Note 1: Method decorators take the class stereotype object, or the class target for static methods. The method name; The property descriptor of the method, which we use a lot heredescriptor.valueanddescriptor.writable
  • Note 2: Descriptor. Value points to functions, which we can use to override functions
  • Note 3: The instance cannot be used when descriptor. Writable = falsetoast.tell = ...Let’s rewrite the function

Parameter decorator

An example of a parameter decorator is:

/ / parameter decorator const initText: ParameterDecorator = (target, key, index) = > {}Copy the code
  • Note: the inserted parameters are the prototype (target), the method name (key), and the position of the parameter in the parameter set (index,0 start).

It is often used with other decorators.

It’s okay not to look.

Decorators on different declarations in a class will be applied in the following order:

  1. Parameter decorators, followed by method decorators, and attribute decorators are applied to each instance member.
  1. Parameter decorators, followed by method decorators, and attribute decorators are applied to each static member.
  1. Parameter decorators are applied to constructors.
  1. Class decorators are applied to classes.

Tips to check out:

The same level decorators are loaded from the top down, but called from the bottom up. Even if there are several decorators of the same level with the same function, the top decorator will overwrite the bottom decorator

conclusion

With a general overview of classes and decorators, its usage and role are outlined. Looking back at the beginning of a chestnut given, is not a lot of clear thinking? After that, at least in frameworks like NestJS or Angular, you shouldn’t encounter decorators

Decorator, class, on the other hand, is really a feature of object-oriented programming. Some of the functions that can be done using a prototype chain or a class can be tried using a class later. Being familiar with object-oriented programming style will not only help broaden your mind, but also help you find objects 🧐

By the way, if you like this series, might as well pay attention to me, learn together, progress together!