React でも Angular みたいに DI したい！

When

2017/02/07

At

[Meguro.es #8 @ アカツキ](https://meguroes.connpass.com/event/47839/)

---

## 自己紹介

ちきさん
(Tomohiro Noguchi)

Twitter/GitHub/Qiita: [@ovrmrw](https://twitter.com/ovrmrw)

ただのSIer。

Angular Japan User Group (ng-japan)スタッフ。

![3a2512bb-aa72-4515-af42-1f1721252f39.jpg](https://qiita-image-store.s3.amazonaws.com/0/74793/c753b39c-1581-ea1d-87d1-181f37bda60d.jpeg)

---

### アカウントIDの由来

1. the day after tomorrow 
1. overmorrow(俗語)
1. 略して
1. ovrmrw
1. 「先を見据えて」よりさらに先を、みたいな感じです。

(よく聞かれるので:innocent:)

---

(ここからInversifyJSの基本の話)

===

GitHubリポジトリ [ovrmrw/inversify-mock-example](https://github.com/ovrmrw/inversify-mock-example)

---

### モチベーション: ReactだってDIしたい:tired_face:

---

## InversifyJS

- a library for Inversion of Control
- Angularのように `@injectable` とか `@inject` とか書いてDependency Injection(依存性注入)できます。

![C19WVJ9XAAAiv8n.jpg](https://qiita-image-store.s3.amazonaws.com/0/74793/68e5516a-e1f6-a6ef-5947-31c54daf6fe7.jpeg)

---

### 事前準備 (TypeScriptの場合)

`tsconfig.json`でデコレーターを有効にします。

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

なるべく最初の方で`reflect-metadata`を読み込みます。(polyfill)

```ts:index.ts
import 'reflect-metadata'
```

---

まずInjectするクラスを作ります。
DIコンテナの中でインスタンス化させるクラスは`@injectable`デコレーターが必須です。

```ts:services.ts
import { injectable } from 'inversify'

@injectable()   // ★
export class Katana {
  hit() { 
    return 'cut!' 
  }
}

@injectable()   // ★
export class Shuriken {
  throw() { 
    return 'hit!' 
  }
}
```

---

DIコンテナの定義を書きます。慣例としてファイル名は`inversify.config.ts`とします。

```ts:inversify.config.ts
import { Container } from 'inversify'
import { Katana, Shuriken } from './services'

const rootContainer = new Container()
rootContainer.bind(Katana).toSelf()    // ★
rootContainer.bind(Shuriken).toSelf()  // ★

export const container = rootContainer.createChild()
```

---

### 補足説明

```ts
container.bind(Katana).toSelf()
```

意味: `Katana`トークンに対して`Katana`クラスのインスタンスをInjectする。

===

モックする場合は？

```ts
container.bind(Katana).to(MockKatana)
```

意味: `Katana`トークンに対して`MockKatana`クラスのインスタンスをInjectする。

---

`Ninja`クラスに`Katana`,`Shuriken`クラスをInjectしてみる。

```ts:index.ts
@injectable()
class Ninja {
  constructor(
    @inject(Katana) private katana: Katana, // @inject(Katana)は省略可
    @inject(Shuriken) private shuriken: Shuriken, // @inject(Shuriken)は省略可
  ) { }

  fight() { 
    return this.katana.hit() 
  }
  sneak() { 
    return this.shuriken.throw() 
  }
}

@injectable()
class MockKatana implements Katana {
  hit() { 
    return 'cut! (mock)' 
  }
}

container.bind(Ninja).toSelf()
container.bind(Katana).to(MockKatana)  // ★

const ninja = container.get(Ninja)     // ★

console.log(ninja.fight()) // output: "cut! (mock)"
console.log(ninja.sneak()) // output: "hit!"
```

---

### 補足説明

```ts
  constructor(
    @inject(Katana) private katana: Katana, // @inject(Katana)は省略可
    @inject(Shuriken) private shuriken: Shuriken, // @inject(Shuriken)は省略可
  ) { }
```

`Ninja`クラスの`constructor`で

- `Katana`トークンにbindされたクラスをインスタンス化して変数`katana`にInjectする。
- `Shuriken`トークンにbindされたクラスをインスタンス化して変数`shuriken`にInjectする。

ということが行われています。
そして変数`katana`には`Katana`クラスのインスタンスではなく、

```ts
container.bind(Katana).to(MockKatana)
```

上記の一行によって`MockKatana`クラスのインスタンスがInjectされます。
もしこの行が無かったら、

```ts:inversify.config.ts
rootContainer.bind(Katana).toSelf()
```

`rootContainer`の定義が採用されて`Katana`クラスがInjectされます。

---

### 図で説明 (1/2)

![container-01.png](https://qiita-image-store.s3.amazonaws.com/0/74793/606a5a9a-5a0d-163d-ef7a-7b470f9e5837.png)

---

### 図で説明 (2/2)

![container-02.png](https://qiita-image-store.s3.amazonaws.com/0/74793/f359d37f-b965-cea6-64aa-abed42aac8ab.png)

- ChildContainerで上位のContainerの定義を上書きできる。
- ChildContainerでbindされたクラスが見つからなかったら上位のContainerを辿って探す。

---

### さらに補足説明

`Ninja`クラスのインスタンスを取得するときに`new Ninja()`のように書いてはいけません。

```ts
const ninja = container.get(Ninja)
```

上記のように`container.get(Ninja)`と書くことでDIコンテナから`Ninja`クラスのインスタンスを取り出します。
依存性の解決はDIコンテナの中でよしなにやってくれるというわけです。

---

(ここからReactの話、つまり本題)

===

GitHubリポジトリ [ovrmrw/meguroes-react-inversify-typescript](https://github.com/ovrmrw/meguroes-react-inversify-typescript)

---

まずInjectする適当なクラスを作ります。
`@injectable()`を付けるのがコツですね。

```ts:actions.ts
import { injectable } from 'inversify'

@injectable()   // ★
export class Actions {
  goo(): string {
    return 'goo!'
  }

  choki(): string {
    return 'choki!'
  }

  paa(): string {
    return 'paa!'
  }
}
```

---

DIコンテナの定義を書きます。

```ts:inversify.config.ts
import { Container } from 'inversify'
import getDecorators from 'inversify-inject-decorators'
import { Actions } from './actions'

const container = new Container()
container.bind(Actions).toSelf()   // ★

export const { lazyInject } = getDecorators(container)  // ★
```

`Actions`トークンに`Actions`クラス自身をbindします。

`inversify-inject-decorators`というライブラリを使って`lazyInject`というデコレーターをexportしているのがポイントです。

---

Reactのコンポーネントを書きます。
ようやく今日一番話したかった`@lazyInject(Actions) actions: Actions`が登場しました。

```ts:App.ts
import { lazyInject } from './inversify.config'
import { Actions } from './actions'

export class App extends React.Component<{}, {}> {
  @lazyInject(Actions) actions: Actions   // ★ constructorですらない

  goo(event): void {
    this.setState({ janken: this.actions.goo() })
  }

  choki(event): void {
    this.setState({ janken: this.actions.choki() })
  }

  paa(event): void {
    this.setState({ janken: this.actions.paa() })
  }

  random(event): void {
    const random = Math.random()
    if (random > 0.66) {
      this.goo(event)
    } else if (random > 0.33) {
      this.choki(event)
    } else {
      this.paa(event)
    }
  }

  render() {
    return (
      <div>
        <button onClick={(e) => this.goo(e)}>グー</button>
        <button onClick={(e) => this.choki(e)}>チョキ</button>
        <button onClick={(e) => this.paa(e)}>パー</button>
        <button onClick={(e) => this.random(e)}>ランダム</button>
        <h1>{this.state.janken}</h1>
      </div>
    )
  }
}
```

---

(改行してもしなくてもどちらでも良い)

```ts
  @lazyInject(Actions) actions: Actions
```

```ts
  @lazyInject(Actions) 
  actions: Actions
```

意味: `Actions`トークンに`Actions`クラスのインスタンスをInjectする。ただし**インスタンス生成時ではなく実行時にInjectされる。** 

(追記)...と思ったけど`constructor`の中で`this.actions`を参照してもエラーにはならないのでインスタンス生成時にうまいことInjectしてるのかも。

---

Q: lazyである必要があるの？

A: 本来であれば`container.get(App)`のようにInversifyJSがインスタンス生成のタイミングを握らなければならないが、Reactが握っているため仕方なく後からInjectする必要がある。

---

READMEにもこのように書いてあります。

> Some frameworks and libraries take control over the creation of instances of a given class. For example, React takes control over the creation of instances of a given React component. This kind of frameworks and libraries prevent us from being able to use constructor injection and as a result they are not easy to integrate with InversifyJS.

> いくつかのフレームワークとライブラリは、特定のクラスのインスタンスの作成を制御します。例えば、Reactは与えられたReactコンポーネントのインスタンスの作成を制御します。この種のフレームワークやライブラリは、コンストラクタインジェクションを使用できないため、InversifyJSとの統合が容易ではありません。

---

## ReactでもAngularのようにDIできる:raised_hands:

---

## Angular使おう:raised_hands:

---

### Thanks!