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!');
}
}
戻り値に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);で取得できます。
Method Decorators
引数は3つです。
function MethodLogging(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log('MethodLogging');
console.log(target);
console.log(propertyKey);
console.log(descriptor);
}
↓enumerableは、
↓の場合、forで回した際などに、nameをスキップさせて、表示させないというものです。
↓configurableは、
↓のfalseにしてきた所を、もう書き換えれなくするものです。
↓この後、例えばenumerableをtrueにしようとするとエラーになります。
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;
}
戻り値を使って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');
}
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);
}