TypeScriptのデコレータまとめ

# デコレータとは
クラスを受け取ってデコレーションする関数のこと
```ts
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();
```

```ts:実行結果
Logging...
[Function: User]
User was created
```

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

## デコレータファクトリ
デコレータに引数を渡す場合、デコレータファクトリを使用する。
デコレータとして実行したい関数を無名関数としてリターンする。
```ts
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();
```
```ts:実行結果
$ node dist/decolator.js
Logging User
[Function: User]
```

## デコレータを使用し簡易版のフレームワークを作成
```ts
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();
```


重要なのは以下の部分
```ts
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デコレータを使用するクラスのプロパティを型として使用する事ができる。
```ts
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);
```

## プロパティデコレータ
クラス全体ではなく、クラスを部分的にデコレーションすることができる。
その際は引数を２つ取る。
クラスデコレータよりもプロパティデコレータのほうが先に実行される。
### プロパティにstatic修飾子をつけた場合
```ts
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);
```
```ts:console.log(propertyKey);の出力結果
name2
```
```ts:console.log(target);の出力結果
class User {
    constructor(age) {
        this.age = age;
        console.log('User was created');
    }
}
```

### プロパティにstatic修飾子をつけない場合
```ts
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);
```
プロトタイプになる。
```ts: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()の本当の違い](https://zenn.dev/effor001/scraps/0c1556c5c2b09d)
```ts:PropertyLogging()のconsole.dirの出力結果
ƒ PropertyLogging(target, propertyKey)
length: 2
name: "PropertyLogging"
prototype: {constructor: ƒ}
arguments: (...)
caller: (...)
[[FunctionLocation]]: decorator.js:30
[[Prototype]]: ƒ ()
[[Scopes]]: Scopes[2]
```
このプロトタイプが、プロパティデコレータの第一引数としてとるものとなる。

```ts
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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/637441/188477c4-6639-de8f-0d22-7b07a4384dde.png)

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