DEV Community

Masui Masanori
Masui Masanori

Posted on

[TypeScript] Try decorators

Intro

This time, I will try decorators.
At first I wanted to implement init only setter property of C# in TypeScript.

As a result, I couldn't do that in this time.
Because the decorators are executed only on runtime.

So I just tried using them in this time.

Environments

  • TypeScript ver.4.4.3
  • Webpack ver.5.56.0
  • webpack-cli ver.4.8.0

About decorators

I think the decorators are special kinds of functions for classes and class members.
I can't use them for functions, enums, and etc.

// Compile error: Decorators are not valid here
@message("Hello World")
export function funcSample() {
}
Enter fullscreen mode Exit fullscreen mode

I can add decorators into classes, methods, properties, accessors, and paramters.
They are separated by their arguments.

main.page.ts

import { Sample } from "./sample";

function init(): void {
    const s = new Sample();
    s.userName = 'Masanori';
    s.greet('Hello!');
}
init();
Enter fullscreen mode Exit fullscreen mode

sample.ts

import { classDecorator, methodDecorator, methodDecoratorFactory } from "./decoratorSample";

// Class decorators
@classDecorator
export class Sample
{
    private _userName = '';
    // Accessor decorators(get/set)
    // as same as the method decorators.
    @methodDecorator
    public get userName() {
        return this._userName;
    }
    public set userName(value: string) {
        this._userName = value;
    }
    public constructor() {
        console.log("Hello constructor");
    }
    // Method decorators
    @methodDecorator
    // use factory functions to add any arguments
    @methodDecoratorFactory("Hello world!")
    public greet(message: string): void {
        console.log('greet');
        console.log(`${this._userName}: ${message}`);
    }
}
Enter fullscreen mode Exit fullscreen mode

decoratorSample.ts

export function classDecorator(constructor: Function): void {
    console.log("classDecorator");
    console.log(constructor);
}
export function methodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  console.log('methodDecorator');
  console.log(target);
  console.log(propertyKey);
  console.log(descriptor);
}
export function methodDecoratorFactory(args: string) {
  console.log('methodDecoratorFactory');
  console.log(args);

  return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => 
      methodDecorator(target, propertyKey, descriptor);
}
Enter fullscreen mode Exit fullscreen mode

Result

methodDecorator
{constructor: ƒ, greet: ƒ}
userName
{enumerable: false, configurable: true, get: ƒ, set: ƒ}
methodDecoratorFactory
Hello world!
methodDecorator
{constructor: ƒ, greet: ƒ}
greet
{writable: true, enumerable: false, configurable: true, value: ƒ}
methodDecorator
{constructor: ƒ, greet: ƒ}
greet
{writable: true, enumerable: false, configurable: true, value: ƒ}
classDecorator
class Sample {
    _userName = '';
    get userName() {
        return this._userName;
    }
    set userName(value) {
        this._userName = value;
    }
    constructor() {
        console.log…
Hello constructor
greet
Masanori: Hello!
Enter fullscreen mode Exit fullscreen mode

Accessing or modifying classes and class members

For example, I can call caller methods from the decorators.

decoratorSample.ts

export function classDecorator(constructor: Function): void {
  console.log("classDecorator");
  // call caller methods
  console.log(constructor.prototype['greet']());
}
export function methodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log('methodDecorator');
    // "target" is instance of the "Sample". 
    console.log(target);
}
Enter fullscreen mode Exit fullscreen mode

And I can modify targets like below.

decoratorSample.ts

...
export function ModClassDecorator<T extends { new (...args: any[]): {} }>(constructor: T) {
  console.log("ModClassDecorator");
  return class extends constructor {
    addedText = "added";
  }
}
...
Enter fullscreen mode Exit fullscreen mode

I must use generics to do this.
If I don't use generics and set the argument type as any, I will get an error.

decoratorSample.ts

...
export function ModClassDecorator2(constructor: any) {
  console.log("ModClassDecorator2");
  return class extends constructor {
    addedText = "added";
  }
}
...
Enter fullscreen mode Exit fullscreen mode

Alt Text

Discussion (0)