次のReact状態管理 MobXのStore考察

[先日の投稿](http://qiita.com/Takepepe/items/8172333655442314eb55)に続き第2弾です。MobXではReduxと違い、複数のStoreが存在し、複数のProviderを保持出来ます。ReduxにもProviderがありますが、それはコンポーネントルートに一つあるのみで、初期設計が終われば普段意識することはありません。

MobXでは、Storeをどこで生成し、どうProviderに与えるのが良い設計なのか。この観点に踏み込んだ記事を発見できていないため、独自方針ですが綴ることにしました。（優しいマサカリをお待ちしてます）

# Redux踏襲パターン
Redux実装経験者も、そうでない方も、これが一番わかり易いかと思います。「Providerを一つしかもたない」です。こうしてしまえば、全てのコンポーネントが全ての状態にアクセス出来る状態になりますし、Reduxの1Storeと並列で考えることが出来ます。シンプルさを求めるなら、これで十分でしょう。

``` routes.js
import StoreA from 'stores/storeA'
import StoreB from 'stores/storeB'
import StoreC from 'stores/storeC'
const stores = {
  storeA: new StoreA(),
  storeB: new StoreB(),
  storeC: new StoreC()
}

export const Routes = () => {
  return (
    <Provider { ...stores }>
      <Router history={ history }>
        <Route path="/A" component={ ComponentA } />
        <Route path="/B" component={ ComponentB } />
        <Route path="/C" component={ ComponentC } />
      </Router>
    </Provider>
  )
}

```

でもちょっと待ってください。もう一歩踏み込んで、Storeが大量になっていく状態を想像してみてください。「何十Storeにもなった時、アプリケーション初期化時に new Store して、それで良いの？ 要らないStoreを確保してない？」ということです。

# あなたのSPA、メモリ圧迫してませんか？

SPAで何かしらの状態管理実装を経験された方は、ある時点で気づくはず。Storeの状態は放置しておくと、パンパンになるということに。。画面を表示するうえで不要になった状態を解放する処理を、どこかに実装しなければいけません。もしこの観点を意識されていなかったのなら、非SPAでは直面しなかった問題が現れるのも、時間の問題です。

Reduxの場合、containerがその解放役を担えそうです（自分の場合そこに押し込む）。表示において保持しておく必要のない情報は、アンマウント時に適宜解放、どの条件をもって解放するかは、設計次第。というところかと思います。

MobXにおいては、Containerが存在しないため、上記機能を代替する層が必要になります。そこで、`'react-router/Route'` `'mobx-react/Provider'` `'Store'` を密結合させるパターンが以下のRoute密結合パターンです。

# Route密結合パターン
MobXで実装していると、srcディレクトリにおいて、components、stores、といったファイル群が必ず出来るかと思います。筆者はそれに加えて「providers」ディレクトリを確保し、ProviderでラップされたRoute用コンポーネントを定義しています。

``` routes.js
import rootStore from 'stores/root'
import ProviderA form 'providers/providerA'
import ProviderB form 'providers/providerB'
import ProviderC form 'providers/providerC'

export const Routes = () => {
  return (
    <Provider rootStore={ rootStore }>
      <Router history={ history }>
        <Route path="/A" component={ ProviderA } />
        <Route path="/B" component={ ProviderB } />
        <Route path="/C" component={ ProviderC } />
      </Router>
    </Provider>
  )
}

```

```providerA.js
import { Component } from 'react'
import { Provider }  from 'mobx-react'
import StoreA        from '~/stores/storeA'
import ComponentA    from '~/components/componentA'

export default class ProviderA extends Component {
  componentWillMount() {
    this.store  = new StoreA()
  }
  componentWillUnmount() {
    this.store = void 0 // アンマウント時に削除
  }
  render() {
    return (
      <Provider storeA={ this.store }>
        <ComponentA />
      </Provider>
    )
  }
}
```

かなり雑な例ですが、routingに併せて必要なstore生成を必要な分だけ、生成することが出来ていますね。解放手段も色々検討できそうです。

- マウントする度にAPIを叩きたくない
- アンマウント時に一部の状態だけ解放したい
- 閲覧環境のパフォーマンスを検証したうえで解放したい

こういった要件にも応えることが出来る providers層をあらかじめ持っておくことで、後々のメモリ圧迫危機を回避できそうです。また、MVCフレームワークのコントローラの様な立ち位置にあることが分かるかと思います。

ただし、全てのコンポーネントが全ての Store にアクセス出来る、という構造ではなくなります。コンポーネントがあるStoreを参照するためには、それに紐づけられた Provider に内包されていることが条件です。「ああ…こっちのコンポーネントでも、あのStoreの状態参照したい…」となったら、上階層の ProviderStore に migrate しましょう。解放、保持などの依頼は、該当の Provider で参照したい Store を inject し、action を発行すれば良さそうです。

```providerA.js
import { Component } from 'react'
import { Provider }  from 'mobx-react'
import StoreA        from '~/stores/storeA'
import ComponentA    from '~/components/componentA'

@inject(s => ({
  retainAction:  s.upperStore.someRetainAction
  disposeAction: s.upperStore.someDisposeAction
}))
export default class ArticlesProvider extends Component {
  componentWillMount() {
    this.props.retainAction()
  }
  componentWillUnmount() {
    this.props.disposeAction()
  }
  render() {
    return (
      <Provider articleStore={ this.store }>
        <ComponentA />
      </Provider>
    )
  }
}
```