From C#to TypeScript - Decorators

Keywords: TypeScript Attribute JSON Database

From C#to TypeScript - Decorators

In C#, if you want to add some extra information or functionality to a class or method without directly modifying it, you can think of Attribute, which is a very convenient function decorator.
TypeScript can also use decorators to add additional functionality to classes, functions, attributes, and parameters. Decorators are a proposal from ES7 and are already available in TypeScript, but experimentalDecorators need to be enabled in tsconfig.json.

"compilerOptions": {
    ..., // other options
    "experimentalDecorators": true}

Introduction to Decorators

Decorators in TypeScript can be applied to classes, methods, attributes, and function parameters, and multiple can be applied at the same time.
The ornament is written in @name(), () you can either not, or you can write some parameters inside.

@Testable
@Log('controller')class Controller{

    @GET
    getContent(@QueryParam arg: string): string{        return '';
    }
}

Realization of Decorator

Decorators can be divided into two types according to their implementation:
One is without parentheses, just like attributes, such as @Testable.

function Testable(target: Function) { // Parameters for class, method, property, and method parameters are different
    //Here you can record some information to the target or do some work with the target, such as seal}

The other is bracketed, like a function, such as @Log('controller'), where the parameters in the implementation function are those in brackets and need to return a function.

function Log(name: string) { // name is the parameter'controller'passed in
    return function(target: Function) { // Parameters for class, method, property, and method parameters are different
        // Here you can do some processing based on name and target
    }
}

Class Decorator

The above (target: Function) is actually an ornament parameter to a class, pointing to its constructor. If you want to add a simple seal function to a class, you can do so:

function sealed(target: Function) {    Object.seal(target);    Object.seal(target.prototype);
}

@sealedclass Test{
    
}

Test.prototype.test = ''; // Runtime error, cannot add

The sealed above is the decorator of a class, and the target refers to the constructor, and the class decorator is such a parameter.

Method decorator

The method decorator has three parameters:

  1. If you decorate a static method, it is the constructor of the class, and if it is an instance method, it is the prototype of the class.

  2. The name of the method.

  3. PropertyDescriptor of the method.
    PropertyDescriptor is the property descriptor and has
    Is configurable, such as dynamically adding and deleting function properties
    Writtable is writable and can be used to set read-only properties
    Whether enumerable s can be enumerated, that is, whether they can be enumerated to in for...in
    Value of a value object or property

With these parameters, it is good to add some functionality to the method, such as the following routes that implement Get in type WebApi:

const Router = Symbol(); // Unique key, the information function GET (path?: string) used to store the decorator {// GET with an optional parameter
    return (target: any, name: string) => setMethodDecorator(target, name, 'GET', path);
}//Save the method and path and use the function setMethodDecorator(target: any, name: string, method: string, path?: string) when routing.
    target[Router] = target[Router] || {};
    target[Router][name] = target[Router][name] || {};
    target[Router][name].method = method;
    target[Router][name].path = path;
}// Set enumerablefunction Enumerable(enumerable: boolean) via PropertyDescriptor { 
    return (target: any, name: string, descriptor: PropertyDescriptor) => {
        descriptor.enumerable = enumerable;
    };
}class Controller{

    @GET
    @Enumerable(true)
    getContent(arg: string): string{        return '';
    }
}

Parameter Decorator

Method parameters can also have adorners, as well as three parameters. The first two parameters are consistent with the method, and the last parameter is the position of the adorned parameter.
The parameter decorator allows you to dynamically check or set parameter values for a method. Below is a check to see if the parameter is empty and throw an exception if it is empty.

const CheckNullKey = Symbol();const Router = Symbol();// Save the CheckNull decoration parameters in function CheckNull(target: any, name: string, index: number) {
    target[Router] = target[Router] || {};
    target[Router][name] = target[Router][name] || {};
    target[Router][name].params = target[Router][name].params || [];
    target[Router][name].params[index] = CheckNullKey;
}// Find out the parameter of CheckNull and check the parameter value, throw an exception if it is empty, otherwise continue executing method function Check (target: any, name: string, descriptor: PropertyDescriptor) {let method = descriptor.value;
    descriptor.value = function () {        let params = target[Router][name].params;        if (params) {            for (let index = 0; index < params.length; index++) {                if (params[index] == CheckNullKey &&  // Find the parameter of CheckNull and throw an exception
                    (arguments[index] === undefined || arguments[index] === null)) {                    throw new Error("Missing required argument.");
                }
            }
        }        return method.apply(this, arguments);
    }
}class Controller{

    @Check
    getContent(@CheckNull id: string): string{        console.info(id);        return id;
    }
}new Controller().getContent(null); // error : Missing required argument.

Attribute Decorator

Usage As above, there are only two parameters, which, like the first two of the class decorators, are often used to identify attributes.

function Column(target: any, name: string) {    //Save the name, a column that simply identifies the column in the database and is often used in ORM frameworks}class Table{

    @Column  
    name: string;
}

There are also decorators for property accessors, which are basically the same three parameters as the method, but get and set of the same property can only have one, and must be the one declared first.

class User {    private _name: string;

    @Enumerable(true)    get name(){        return this._name;
    }    set name(value: string) {        this._name = value;
    }
}

Execution order of multiple decorators

A declaration can add more than one decorator, so there is a sequence of executions.
The decorator function is first executed from top to bottom, and then the function returned by the bracketed decorator is applied from bottom to top.

function Test1(){    console.info('eval test1');    return function(target: any, name: string, descriptor: PropertyDescriptor){        console.info('apply test1');
    }
}function Test2(){    console.info('eval test2');    return function(target: any, name: string, descriptor: PropertyDescriptor){        console.info('apply test2');
    }
}class User1{

    @test1()
    @Test2()
    getName(){

    }
}

The result is:

eval test1eval test2
apply test2
apply test1

In summary, the decorator is equivalent to introducing a natural decoration pattern, adding additional functions to classes, methods, and so on.However, the decoration is not so stable at present, but due to its convenience, there are already mature projects in use.


Posted by drunknbass on Fri, 05 Jul 2019 09:38:49 -0700