Reduxを学び始めたばかりの時、ReduxのチュートリアルのReactのComponentと連携しているコードが何をやっているかわからなかったのだが、コードを書いているうちに少しずつわかってきたので、自分の考えの整理のためと同じところで躓いている人のために順序立てて説明していきたい。
まず、Fluxの処理の流れを再確認しておきたい。Fluxの概念図はググれば色々出てくるが、まずAction,Store,Componentで以下のように表してみる。
Action —–> Store —–> Component (—–> Action…)
ここで、Componentに目を向けてみるが、ComponentはStoreからstateを得て、必要に応じてActionを発行する。(実際にActionを発行するのはActionCreaterなのでComponentはそれを呼ぶということになる。)
さて、Actionは{type: 'addTodo'}
のようなただのオブジェクトだが、この発行されたActionをどのようにStoreに伝えるのか。それはDispatcherの役割である。つまり、発行したActionをDispatcherに渡さないといけない。実装ではこれもComponentに書く。つまり、ComponentはActionを発行するだけでなく、その発行したActionをDispatcherに渡す処理を書く。
さて、ReduxではそのDispatcherはどこにあるのか。
実はReduxにはDispatcherが無い。その代わりStoreにdispatchというメソッドが用意されている。これはReduxがStoreを単一として設計しているからだろう。そのようなわけで、store.dispatch(action)というような処理をComponent内に書けば、
Component —–> Action —–> Store
と流すことができる。
では、どのようにComponentは単一storeを得るのか。それは親コンポーネント(ルートコンポーネント)からもらうしかない。
チュートリアルを見るとReduxとReactの連携にReactReduxというライブラリが使われている。このライブラリは必須ではない。しかし、このライブラリを使うことにっよて、親から子へstoreを渡すというような先に述べた処理を書かずに済む。一方、初めてReduxとReactの連携しているコードを見た時、何をやっているのか混乱しやすい。
さて、Reduxは単一storeなのでそのstoreはまずルートコンポーネントに渡され、子に渡されていく。チュートリアルのindex.jsに
render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
とあるが、これはルートコンポーネントにstoreを渡している。このProviderというのはReactReduxのコンポーネントである。なぜこれで子のコンポーネントがstoreを得られるようになるか。それは、Reactが提供する機能には、propsのようにいちいち子のコンポーネントに明示的に値を渡す必要のないcontextというのがあり、ReactReduxも内部でそれを使っているからだ。そのため<App store={store} />
と書く必要がないのである。この機能を知らないと子のコンポーネントがどのようにstoreを得ているのかわかりづらい。
では次に子のコンポーネントがstoreを得る方法、本題のReactとComponentの連携している部分を説明していく。
Reduxと連携するComponentはContainerComponentと呼ばれる。Reduxと連携と言っているが、Componentがstoreを得られるようになることを連携と言っているのである。ただ、ReactReduxを利用した場合、ReactReduxがstoreを得て、そこから必要な機能だけがComponentに渡される。
ReduxとComponentを連携しているコードを見ると、ReactRedux.connect()(Component)
のような記述があるが、これを実行することにより、Componentのpropsにdispatchという関数が渡される。このdispatch関数にActionを渡すことによって
Component —–> Action —–> Store
と処理を流すことができるのである。以下、具体的なコードである。
import React from 'react' import * as ReactRedux from 'react-redux' import * as actions from '../actions/index.js' class MyComponent extends React.Component { handleClick() { //propsにdispatch関数がある this.props.dispatch(actions.addTodo); } render() { return ( <div> <button onClick={this.handleClick.bind(this)} /> </div> ); } } export default ReactRedux.connect()(MyComponent);
さて、これでComponent —–> Action —–> Storeと処理を流すことができた。では、Componentはstoreからどのようにstateを得るのか。つまりStore —–> Componentについてである。
これはRedux.connectの第一引数に渡す関数で得ることができる。チュートリアルではmapStateToPropsと命名されている関数のことだ。このmapStateToPropsの第一引数にstoreのstateが渡される。mapStateToPropsの返り値はObjectで、このObjectのプロパティがそのままComponentのpropsに渡される。state全部を渡したいなら返り値にstateを指定する。
import React from 'react' import * as ReactRedux from 'react-redux' import * as actions from '../actions/index.js' class MyComponent extends React.Component { render() { //stateが全て入っている console.log(this.props); return (<div />); } } const mapStateToProps = (state) => { return state; } export default ReactRedux.connect(mapStateToProps)(MyComponent);
stateの中の一部を取り出したいなら以下のように返り値を指定する。
const mapStateToProps = (state) => { return { partOfState: state.partOfState }; }
こうしてComponentはstateを得ることができ、dispatchも得ることができたので、
Action —–> Store —–> Component( —–> Action…)
の流れに忠実な実装にできるということが理解できたと思う。
ReduxとReactの連携ではReactReduxが何をやっているか概観を理解することが重要である。QiitaにReactReduxを使ったときと使わなかったときの比較が載っていたので参照してほしい。
少し冗長な説明になってしまったが、もしわからなくても、実際にコードを書いているうちに理解できる範囲だと思われる。自身もまだ学んでいる途中なのでもし変な部分があれば指摘していただければ嬉しい。