TSyringeを動かしてみた

TSyringeを動かしてみたのでそのメモ

**注意** 動かしてみた結果と、推測を書いているので誤っている可能性もあります😅

## TSyringeとは
Microsoftが開発している Javascript/TypeScript用の軽量DIコンテナです。
コンストラクタインジェクションのみサポートしています。

## 準備
今回はバージョン4.1.0 を使いました。

動作確認はブラウザ上でコンソールに出力して確認しました。

```
rem typescript 使うので
yarn add typescript -D

rem tsyringeの準備
yarn add tsyringe reflect-metadata

rem webpackを使う
yarn add webpack webpack-cli webpack-dev-server ts-loader -D

rem 動作確認はブラウザでしたかったので
yarn add html-webpack-plugin -D

rem tsconfig 作る
yarn tsc --init
```

デコレータを使うので`tsconfig.json` の以下の部分の設定を変更します。

```json:tsconfig.json
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}
```

webpackの設定周りは省略します。

## 確認1 `injectable` を試す
とりあえずシンプルに動かしてみます。

一番末端のクラス

```ts:FooService.ts
import { injectable } from "tsyringe";

@injectable()
export default class FooService {

    public constructor() {
        console.log("new FooService()")
    }

    doFoo(value: string) {
        return `${value} : Foo!`
    }
}
```

`FooService` を使うクラス

```ts:HogeService.ts
import { injectable } from "tsyringe";
import FooService from "./FooService";

@injectable()
export default class HogeService {

    constructor(
        private fooService: FooService
    ) {
        console.log("new HogeService()")
    }

    doHoge(value: string) {
        return `${value} : Hoge!`
    }

    doHogeFoo(value: string) {
        return this.fooService.doFoo(this.doHoge(value))
    }
}
```

エントリポイント

```ts:index.ts
//Reflect API を使うためのPolyfill
import "reflect-metadata";
import { container } from "tsyringe";
import FooService from "./FooService";
import HogeService from "./HogeService";

//FooServiceを実行
console.log("---------")
const fooService = container.resolve(FooService);
console.log(fooService.doFoo("test01"));

//HogeServiceを実行
console.log("---------")
const hogeService = container.resolve(HogeService);
console.log(hogeService.doHogeFoo("test02"))
```

実行結果は以下のようになります。

```
---------
new FooService()
test01 : Foo!
---------
new FooService()
new HogeService()
test02 : Hoge! : Foo!
```

特に何も言うことはないです😀

### 確認1-2 `FooService` の`@injectable()` を消してみる

```ts:FooService.ts
import { injectable } from "tsyringe";

//@injectable()
export default class FooService {

    public constructor() {
        console.log("new FooService()")
    }

    doFoo(value: string) {
        return `${value} : Foo!`
    }
}
```


実行結果は先ほどと同じになります。

```
---------
new FooService()
test01 : Foo!
---------
new FooService()
new HogeService()
test02 : Hoge! : Foo!
```

### 確認1-3 `HogeService` の`@injectable()` を消してみる

`FooService` の`@injectable()` は復活させておきます。

```ts:HogeService.ts
import { injectable } from "tsyringe";
import FooService from "./FooService";

//@injectable()
export default class HogeService {

    constructor(
        private fooService: FooService
    ) {
        console.log("new HogeService()")
    }

    doHoge(value: string) {
        return `${value} : Hoge!`
    }

    doHogeFoo(value: string) {
        return this.fooService.doFoo(this.doHoge(value))
    }
}
```

これを実行したところ以下のようにエラーになります。

```
---------
new FooService()
test01 : Foo!
---------
dependency-container.js:210 Uncaught Error: TypeInfo not known for "HogeService"
    at InternalDependencyContainer.construct (dependency-container.js:210)
    at InternalDependencyContainer.resolve (dependency-container.js:91)
以下略
```

### 確認1 からわかること
`@injectable` をつけることで、コンテナからインスタンスを取得するときに、コンストラクタに依存パラメータがあれば、それも解決してくれるようです。

`FooService` の`@injectable`を消しても動いていたのは、何も依存するものがないからエラーが起きていなかったようです。

このあたりのことは[injectable.ts](https://github.com/microsoft/tsyringe/blob/v4.1.0/src/decorators/injectable.ts#L13)でパラメータ情報を登録している(のかな？)ことや
`dependency-container.ts` の [resolveメソッドのこのあたり](https://github.com/microsoft/tsyringe/blob/v4.1.0/src/dependency-container.ts#L192) および[constructメソッド](https://github.com/microsoft/tsyringe/blob/v4.1.0/src/dependency-container.ts#L339)
を見れば確認ができます。


## 確認2 `singleton` を試す
`確認1`とほとんど同じですが`FooService`のみ`@injectable()` を `@singleton()`に変更します。

```ts:FooService.ts
import { singleton } from "tsyringe";

//↓ここを変更した。
@singleton()
export default class FooService {

    public constructor() {
        console.log("new FooService()")
    }

    doFoo(value: string) {
        return `${value} : Foo!`
    }
}
```

`HogeService.ts` は変更なし(`@injectable()` はつけています。)

`index.ts` も変更なしです。

実行結果は以下のようになります。

```
---------
new FooService()
test01 : Foo!
---------
new HogeService()
test02 : Hoge! : Foo!
```

`new HogeService()` の前にあった `new FooService()` が消えています。

他のDIコンテナと同じように`@singleton()` をつけると、コンテナ内で一つのインスタンスが再利用されるようです。

[singleton.ts](https://github.com/microsoft/tsyringe/blob/v4.1.0/src/decorators/singleton.ts#L14) を見ると、コンテナにシングルトンとして登録しているようです。

## 確認3 `autoInjectable` を試してみる
`autoInjectable`を使うとコンテナから取り出すのではなく`new` するだけで依存関係を解決してくれるようです。(すげぇ)

```ts:FooService.ts
import { injectable } from "tsyringe";

@injectable()
export default class FooService {

    public constructor() {
        console.log("new FooService()")
    }

    doFoo(value: string) {
        return `${value} : Foo!`
    }
}
```

`@autoInjectable()` を付与したクラスです。注意点としてコンストラクタのパラメータに`?` をつけます。

```ts:AutoHogeService.ts
import { autoInjectable } from "tsyringe";
import FooService from "./FooService";

@autoInjectable()
export default class AutoHogeService {

    constructor(
        // ?をつけておかないと new AutoHogeService() とできずコンパイルエラーになる。
        private fooService?: FooService
    ) {
        console.log("new AutoHogeService()")
    }

    doHoge(value: string) {
        return `${value} : Hoge!`
    }

    doHogeFoo(value: string) {
        return this.fooService!.doFoo(this.doHoge(value))
    }
}
```

```ts:index.ts
import "reflect-metadata";
import AutoHogeService from "./AutoHogeService";

console.log("------");
const autoHogeService1 = new AutoHogeService();
console.log(autoHogeService1.doHogeFoo("test1"));

console.log("------");
const autoHogeService2 = new AutoHogeService();
console.log(autoHogeService2.doHogeFoo("test2"));
```

実行結果は以下のようになります。

```
------
new FooService()
new AutoHogeService()
test1 : Hoge! : Foo!
------
new FooService()
new AutoHogeService()
test2 : Hoge! : Foo!
```

`new AutoHogeService()` を実行したときに `new FooService()` も実行されているようです。

[auto-injectable.ts](https://github.com/microsoft/tsyringe/blob/v4.1.0/src/decorators/auto-injectable.ts) を見ても何やら`resolve` していますね。(詳細は解読できていないのでわかりません..)

## おわりに
今回は `injectable`, `singleton`, `autoInjectable` を試してみました。

長くなったのでこの辺で終わります。

続きは[こちら](https://qiita.com/epsilon_87_myo/items/a3a8eb3bf492230d419f)