[redux] Presentational / Container componentの分離 - react-redux.connect()のつかいかた

ReduxをReactと同時につかうときは，コンポーネントをPresentational componentとContainer componentに分離することがプラクティスの一つらしいです．
このための道具として`connect()`関数が`react-redux`パッケージで提供されています．

http://redux.js.org/docs/basics/UsageWithReact.html
に詳しいです．

表示のみに専念するPresentational componentとロジックのみに専念するContainer componentを分離することで，コンポーネントの再利用性を高めることができる，らしいです．

自分の勉強のために自分なりの要点だけ以下にまとめてみました．
ES6記法を使っています．

# Presentational component

Presentational componentsは見た目だけを扱うコンポーネントです．
基本的にstateには触らず，propsとして与えられるデータを表示することに専念します．
storeにもアクセスしません．なので，dispatchもできません．
例えばボタンを表示しても，`onClick`ではpropsで与えられるコールバック関数を呼ぶだけです．
こうすることで，表示するデータや，ボタン押下時の処理を外部から指定することができ，再利用性が上がります．

_追記 (編集リクエストありがとうございます): dropdownの開閉状態のような、componentの中に閉じ込めた方が良いと判断されるデータの管理にはstateを使うこともあります．そういうのは大抵UIに関する状態管理です．アプリケーションの状態やデータはReduxのstoreに格納し、container comoponentからアクセスすることになるでしょう．_


たとえば，こんな感じ．

```jsx
import React from 'react'

export default React.createClass({
    render() {
        const {
            text,
            onButtonClick,
        } = this.props

        return (
            <div>
                <p>{text}</p>
                <button onClick={onButtonClick}>Click!</button>
            </div>
        )
    }
})
```

表示するデータ`text`はpropsとして受け取り，表示します．具体的に`text`が何になるかについて，Presentational componentは関知しません．
ボタン押下時のコールバック`onButtonClick`もpropsとして受け取ります．これも同様に，`onButtonClick`がどんな処理かについてはPresentational componentは関知しません．

# Container component

Container componentはPresentational componentに具体的なデータやコールバック関数を与えるコンポーネントです．
`react-redux`が提供する`connect()`関数を使って，

```js
import { connect } from 'react-redux'

import SomePresentationalComponent from 'some-presentational-compnent'

const mapStateToProps = (state, ownProps) => {
    return {
        // blah blah blah
    }
}

const mapDispatchToProps = dispatch => {
    return {
        // blah blah blah
    }
}

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(SomePresentationalComponent)
```

のように書くもの，と思っておけばいいと思います．

`mapStateToProps(state, ownProps)`は，`store.getState()`の結果を第一引数に，このContainer componentへ渡されたpropsを第二引数にして呼び出される関数で，
これらのstateとpropsを使って子のPresentational componentにpropsとして渡す値を生成します．

`mapDispatchToProps(dispatch)`は，`store.dispatch`を第一引数にして呼び出される関数で，
子のPresentational componentにpropsとして渡す値（というか，コールバック関数）を生成します．通常，このコールバック関数では，引数として渡されている`dispatch`を呼び出し，子のPresentational ComponentがStoreへActionを送信できるようにしておきます．ボタン押下時に呼ばれる処理などもコールバック関数として書いてPresentational componentにpropsとして渡すと言いましたが，このコールバック関数を作るためのものと思っておけばいいと思います．

`connect()`を使った上記のコードは，

```jsx
import React from 'react'

import SomePresentationalComponent from 'some-presentational-compnent'

export default React.createClass({
    componentWillMount() {
        this.unsubscribe = this.context.store.subscribe(this.forceUpdate)
    },

    componentWillUnmount() {
        this.unsubscribe()
    },
    
    contextTypes: {
        store: React.PropTypes.object,
    },
    
    render() {
        const ownProps = this.props
        const state = this.context.store.getState()
        const dispatch = this.context.store.dispatch

        const mapStateToProps = (state, ownProps) => {
            return {
                // blah blah blah
            }
        }

        const mapDispatchToProps = (dispatch) => {
            return {
                // blah blah blah
            }
        }

        const propsFromState = mapStateToProps(state, ownProps)
        const propsFromDispatch = mapDispatchToProps(dispatch)
        
        return <SomePresentationalComponent
            {...propsFromState}
            {...propsFromDispatch}
        />
    }
})
```

と書くのと同じなのですが，

- contextとして渡されるstoreを明示的に扱わなくて良い
- `store.subscribe(this.forceUpdate)`周りの記述を省略できる（reduxのstateが変更されたら，勝手に表示を更新してくれる）
    - しかも自前でsubscribeするより最適化されている（らしい）
- そもそもコード量が減る

などの利点があります．

# connect()利用法

`connect()`を使うために，ルートノードをマウントするとき，

```jsx
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import App from 'app.react'

const store = configureStore() // create store

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
)
```

のように一番外側を`<Provider>`で囲んで，storeを渡しておく必要があります．

また，`Provider`直下のコンポーネントはPresentational componentであるのがよいらしいです．

```jsx
const App = React.createClass({
    render() {
        return (
            <SomeContainerComponentA />
            <SomeContainerComponentB />
            <SomeContainerComponentC />
        )
    }
})

ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
)
```
みたいな？
こうすると，ルートノードがContainer componentの羅列になってスッキリする？

# ディレクトリ構成

あくまで例ですが，私は自分のプロジェクトのソースディレクトリをこんな感じにしています．
Presentational componentsを`components`以下に，Container componentsを`containers`以下に格納して区別しています．

```
src
├── actions
├── app.js
├── components
│   ├── App.react.js
│   └── Page.react.js
├── containers
│   └── AwesomePage.react.js
└── store
    ├── configureStore.js
    └── reducers
        └── some-reducer.js
```