TypeScript decorators

Posted by alcapone on Tue, 04 Jan 2022 08:10:24 +0100

 

Decorator is a special type of declaration that can be attached to class declarations, methods, accessors, properties or parameters to modify the behavior of a class. The decorator uses @ expression. Expression must be a function after evaluation. It will be called at run time, and the decorated declaration information will be passed in as parameters.

Example:

@Path('/hello')
class HelloService {}

Decorators are also experimental syntax in TypeScript, so to use them, you must use tsconfig. In the configuration file Enabled in JSON compilation options:

{
    "compilerOptions": {
        "experimentalDecorators": true
    }
}

How to define decorators

The decorator itself is actually a function. Theoretically, if parameters are ignored, any function can be used as a decorator. Example:

demo.ts

function Path(target:any) {
    console.log("I am decorator.")
}

@Path
class HelloService {}

After compiling with tsc, execute the command node demo JS, the output results are as follows:

I am decorator.

Trim actuator timing

The modifier changes the behavior of the class when the code is compiled (not TypeScript compilation, but js in the compilation stage of the executor), not at run time. This means that the modifier can run the code at the compilation stage. In other words, the modifier is essentially a function executed at compile time.
On node JS environment will execute as soon as the module is loaded

Solving parameter problems by function coritization

However, in the actual scene, sometimes you want to pass some parameters to the decorator, as follows:

@Path("/hello", "world")
class HelloService {}

At this time, the above decorator method is not satisfied (VSCode compilation error). This is that we can use the function coritization feature in JavaScript

function Path(p1: string, p2: string) {
    return function (target) { //  This is the real decorator
        // do something 
    }
}

Five kinds of decorators

In TypeScript, decorators can modify four statements: classes, properties, accessors, methods, and method parameters.

Type 1 trimmer

Applied to the class constructor, whose parameters are the constructor of the class.
Note that class is not a class in a strongly typed language like Java, but the syntax sugar of a JavaScript constructor.

function Path(path: string) {
    return function (target: Function) {
        !target.prototype.$Meta && (target.prototype.$Meta = {})
        target.prototype.$Meta.baseUrl = path;
    };
}

@Path('/hello')
class HelloService {
    constructor() {}
}

console.log(HelloService.prototype.$Meta);// Output: {baseUrl: '/hello'}
let hello = new HelloService();
console.log(hello.$Meta) // Output: {baseUrl: '/hello'}
2 method decorator

It is applied to the property descriptor of the method and can be used to monitor, modify or replace the method definition.
The method will pass in the following three parameters at runtime:

  • 1. For static members, it is the constructor of the class, and for instance members, it is the prototype object of the class.
  • 2. Name of the member.
  • 3. The property descriptor of the member.
function GET(url: string) {
    return function (target, methodName: string, descriptor: PropertyDescriptor) {
        !target.$Meta && (target.$Meta = {});
        target.$Meta[methodName] = url;
    }
}

class HelloService {
    constructor() { }
    @GET("xx")
    getUser() { }
}

console.log((<any>HelloService).$Meta);

Note: when vscode is edited, it is sometimes reported that when it is called as an expression, the signature of the method modifier cannot be resolved. Error, which needs to be in tsconfig Add the target configuration item in JSON:

{
    "compilerOptions": {
        "target": "es6",
        "experimentalDecorators": true,
    }
}
3 method parameter decorator

The parameter decorator expression will be called as a function at runtime, passing in the following three parameters:

  • 1. For static members, it is the constructor of the class, and for instance members, it is the prototype object of the class.
  • 2. The name of the parameter.
  • 3. The index of the parameter in the function parameter list.
function PathParam(paramName: string) {
    return function (target, methodName: string, paramIndex: number) {
        !target.$Meta && (target.$Meta = {});
        target.$Meta[paramIndex] = paramName;
    }
}

class HelloService {
    constructor() { }
    getUser( @PathParam("userId") userId: string) { }
}

console.log((<any>HelloService).prototype.$Meta); // {'0':'userId'}
4 attribute decorator

The property decorator expression will be called as a function at runtime, passing in the following 2 parameters:

  • 1. For static members, it is the constructor of the class, and for instance members, it is the prototype object of the class.
  • 2. Name of the member.
function DefaultValue(value: string) {
    return function (target: any, propertyName: string) {
        target[propertyName] = value;
    }
}

class Hello {
    @DefaultValue("world") greeting: string;
}

console.log(new Hello().greeting);// Output: world

Decorator loading sequence

function ClassDecorator() {
    return function (target) {
        console.log("I am class decorator");
    }
}
function MethodDecorator() {
    return function (target, methodName: string, descriptor: PropertyDescriptor) {
        console.log("I am method decorator");
    }
}
function Param1Decorator() {
    return function (target, methodName: string, paramIndex: number) {
        console.log("I am parameter1 decorator");
    }
}
function Param2Decorator() {
    return function (target, methodName: string, paramIndex: number) {
        console.log("I am parameter2 decorator");
    }
}
function PropertyDecorator() {
    return function (target, propertyName: string) {
        console.log("I am property decorator");
    }
}

@ClassDecorator()
class Hello {
    @PropertyDecorator()
    greeting: string;


    @MethodDecorator()
    greet( @Param1Decorator() p1: string, @Param2Decorator() p2: string) { }
}

Output results:

I am property decorator
I am parameter2 decorator
I am parameter1 decorator
I am method decorator
I am class decorator

The following conclusions can be drawn from the above examples:

1. When there are multiple parameter decorators: execute forward from the last parameter in turn

2. The parameter decorator in the method and method parameters executes first.

3. Class decorators are always executed last.

4. Method and property decorators, who executes first. Because parameters are part of a method, they are always executed next to the method. In the above example, the attributes and methods are exchanged, and the following results are output:

I am parameter2 decorator
I am parameter1 decorator
I am method decorator
I am property decorator
I am class decorator

Topics: ts