0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Typescript] Decorator

Posted at

Decorator

classを受け取ってデコレートする関数です。
tsconfig.jsonで"experimentalDecorators": trueにする必要があります。

@expressionのような形式で使います。
expressionは、
デコレートされた宣言の情報を持ち、実行時に呼び出される関数として評価されます。

また、decoratorは、classの定義時に実行されています。

↓classの直前に、@Loggingのように、適用させたいDecorator名をつけます。
↓(constructor: Function)は、他のclassでも使えるようにclass全般の型を指定しています。

function Logging(constructor: Function) {
  console.log('Logging...');
  console.log(constructor);
}

@Logging
class User {
  name = 'Quill';
  constructor() {
    console.log('User was created!');
  }
}

Decorator Factories

Decoratorを返す関数を書くことを言います。
この関数を書くことで、Decoratorで引数やパラメータを使えるようになります。

function Logging(message: string) {
  return function (constructor: Function) {
    console.log(message);
    console.log(constructor);
  }  
}

@Logging('Logging User')
class User {
  name = 'Quill';
  constructor() {
    console.log('User was created!');
  }
}

classとはconstructor関数である。

複数のDecoratorを扱う際の順番

Decoratorは下から上に実行されます。
Decorator Factoriesは上から下に実行されます。

function Logging(message: string) {
  console.log('Logging Factory');
  return function (constructor: Function) {
    console.log(message);
    console.log(constructor);
  }  
}

function Component(template: string, selector: string) {
  console.log('Component Factory');
  return function(constructor: { new(): { name: string } }) {
    const mountedElement = document.querySelector(selector);
    console.log('Component');
    const instance = new constructor();
    if (mountedElement) {
      mountedElement.innerHTML = template;
      mountedElement.querySelector('h1')!.textContent = instance.name;
    }
  }
}

@Logging('Logging User')
@Component('<h1>{{ name }}</h1>', '#app')
class User {
  name = 'Quill';
  constructor() {
    console.log('User was created!');
  }
}

Image from Gyazo

戻り値にclassを指定して、新しいclassを作り出す

↓最後のclass Userは、完全にreturn class extends constructorのclassになります。

function Logging(message: string) {
  return function (constructor: Function) {
    console.log(message);
    console.log(constructor);
  }  
}

function Component(template: string, selector: string) {
  console.log('Component Factory');
  return function <T extends { new(...args: any[]): { name: string } } >(constructor: T) {
    
    return class extends constructor {
      constructor(...args: any[]) {
        super(...args);
        const mountedElement = document.querySelector(selector);
        const instance = new constructor();
        if (mountedElement) {
          mountedElement.innerHTML = template;
          mountedElement.querySelector('h1')!.textContent = instance.name;
        }
      }
    }
  }
}

@Logging('Logging User')
@Component('<h1>{{ name }}</h1>', '#app')
class User {
  name = 'Quill';
  constructor(public age: number) {
    console.log('User was created!');
  }
}

Property Decorators

Propertyに対して適用するDecoratorsです。
引数は2つです。
class DecoratorsよりもProperty Decoratorsが先に実行されます。

↓targetは、class UserのProtoTypeになります。

console.dir(Logging);
function PropertyLogging(target: any, propertyKey: string) {
  console.log('PropertyLogging');
  console.log(target);
  console.log(propertyKey);
}

@Logging('Logging User')
@Component('<h1>{{ name }}</h1>', '#app')
class User {
  @PropertyLogging
  name = 'Quill';
  constructor(public age: number) {
    console.log('User was created!');
  }
}

関数はオブジェクトである。

↓メソッドは全部ProtoTypeの中に入る、というのが、
↓classで書いたときのデフォルトの設定になっています。
↓は、class Userが持っているProtoTypeです。
↓そのProtoTypeを、console.log(target);で取得できます。
Image from Gyazo

Method Decorators

引数は3つです。

function MethodLogging(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  console.log('MethodLogging');
  console.log(target);
  console.log(propertyKey);
  console.log(descriptor);
}

PropertyDescriptorは↓に当たります。
Image from Gyazo

↓writableは、書き換えれないものです。
Image from Gyazo

↓enumerableは、
↓の場合、forで回した際などに、nameをスキップさせて、表示させないというものです。
Image from Gyazo

↓configurableは、
↓のfalseにしてきた所を、もう書き換えれなくするものです。
↓この後、例えばenumerableをtrueにしようとするとエラーになります。
Image from Gyazo

Accessor Decorators

Method Decoratorsと一緒です。

function AccessorLogging(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  console.log('AccessorLogging');
  console.log(target);
  console.log(propertyKey);
  console.log(descriptor);
}

  @AccessorLogging
  get age() {
    return this._age;
  }
  set age(value) {
    this._age = value;
  }

Image from Gyazo

戻り値を使ってMethod Decoratorsを実践的にする

Class Decoratorはclassを返すことができました。
Property Decoratorsは何も返しませんでした。

MethodとAccessorは、Descriptorを返すことができます。

↓返す値によって、Property Decoratorsを変換することができます。
↓今回だったら@enumerable(false)で変換しています。

function enumerable(isEnumerable: boolean) {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    return {
      enumerable: isEnumerable
    }
  }
}

  @AccessorLogging
  get age() {
    return this._age;
  }
  set age(value) {
    this._age = value;
  }
  @enumerable(false)
  @MethodLogging
  greeting() {
    console.log('Hello');
  }

Image from Gyazo

Parameter Decorators

パラメータに付けることができるデコレータです。
引数は3つで、3つ目の引数にparameterIndexをつけます。
parameterIndexは、パラメータが何番目に来ているかを表現します。

function ParameterLogging(target: any, propertyKey: string, parameterIndex: number) {
  console.log('ParameterLogging');
  console.log(target);
  console.log(propertyKey);
  console.log(parameterIndex);
}

  greeting( @ParameterLogging message: string) {
    console.log(message);
  }

↓の0は、配列のように、Parameter Decoratorsが0番目にあるよーと言っています。
Image from Gyazo

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?