0
0

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 1 year has passed since last update.

TypeScriptのデコレータまとめ

Posted at

デコレータとは

クラスを受け取ってデコレーションする関数のこと

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

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

const User1 = new User();
実行結果
Logging...
[Function: User]
User was created

デコレータとして定義したLogging()は、クラスのインスタンスが生成される前に実行されている。
デコレータの引数の型はFunctionとなることに注意(クラスはコンストラクタ関数の等位構文)。

デコレータファクトリ

デコレータに引数を渡す場合、デコレータファクトリを使用する。
デコレータとして実行したい関数を無名関数としてリターンする。

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')
    }
}

const User1 = new User();
実行結果
$ node dist/decolator.js
Logging User
[Function: User]

デコレータを使用し簡易版のフレームワークを作成

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

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

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

const User1 = new User();

重要なのは以下の部分

return function (constructor: { new(...args: any[]): { name: string } }) {
        const mountedElement = document.querySelector(selector);
        const instance = new constructor();

Userクラスのnameプロパティを使用するため、newでインスタンスを生成し定数instanceに代入している。
無名関数の第一引数constructorの型としてnew()が使用されているが、これはconstructorが「関数」なのか「コンストラクタ関数」なのかを判別するために使用している。関数はnewできないから、この場合はconstructor関数(クラス)だと指定している。

※jsのクラスはコンストラクタ関数の等位構文で、クラスを関数で表現している。

戻り値に新しいクラスを指定する

ジェネリクスを使用することで、Componentデコレータを使用するクラスのプロパティを型として使用する事ができる。

function Component(template: string, selector: string) {
    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;
                }
            }
        }
    }
}

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

const User1 = new User(23);

プロパティデコレータ

クラス全体ではなく、クラスを部分的にデコレーションすることができる。
その際は引数を2つ取る。
クラスデコレータよりもプロパティデコレータのほうが先に実行される。

プロパティにstatic修飾子をつけた場合

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

class User {
    @PropertyLogging
    static name2 = 'Quill';
    constructor(public age:number) {
        console.log('User was created')
    }
}

const User1 = new User(23);
console.log(propertyKey);の出力結果
name2
console.log(target);の出力結果
class User {
    constructor(age) {
        this.age = age;
        console.log('User was created');
    }
}

プロパティにstatic修飾子をつけない場合

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

class User {
    @PropertyLogging
    name2 = 'Quill';
    constructor(public age:number) {
        console.log('User was created')
    }
}

const User1 = new User(23);

プロトタイプになる。

console.log(target);の出力結果
{constructor: ƒ}
constructor: class User
length: 1
name: "User"
prototype: {constructor: ƒ}
arguments: (...)
caller: (...)
[[FunctionLocation]]: decorator.js:37
[[Prototype]]: ƒ ()
[[Scopes]]: Scopes[2]
[[Prototype]]: Object

プロトタイプとは?

関数をconsole.dir()で確認するとオブジェクトであることが確認できる。
今更聞けないconsole.log()とconsole.dir()の本当の違い

PropertyLogging()のconsole.dirの出力結果
ƒ PropertyLogging(target, propertyKey)
length: 2
name: "PropertyLogging"
prototype: {constructor: ƒ}
arguments: (...)
caller: (...)
[[FunctionLocation]]: decorator.js:30
[[Prototype]]: ƒ ()
[[Scopes]]: Scopes[2]

このプロトタイプが、プロパティデコレータの第一引数としてとるものとなる。

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

class User {
    @PropertyLogging
    name2 = 'Quill';
    constructor(public age:number) {
        console.log('User was created')
    }
    greeting() {
        console.log('hi');
    }
}

const user1 = new User(23);

greeting()メソッドを追加してみる.
age,name2はひと目で確認できるが、greeting()はPrototypeの下にある。
コンストラクタ関数(クラス)のプロパティが、そのコンストラクタ関数(クラス)から生成されるインスタンスにコピーされる。そのコピーされるものをPrototypeという。

スクリーンショット 2022-04-24 15.35.06.png

これはメモリを節約するため。
クラスから生成したインスタンスは、プロパティは違うがメソッドが一緒。
だからPrototypeの中に共通のメソッドを保存している。

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?