はじめに
この記事はだいぶ古くなっており、現在はReact Hooksを使ったReduxとComponentの繋ぎこみ方法があるようです。この記事ではそのあたり触れられてないので注意です。(Reduxの基本的な記事としてはまだ使えると思います、、、思いたい。)
React-Reduxを人に教える機会があるのですが、中々キャッチアップが難しいFWです。
自分自身もReact自体未経験のところから、それなりに規模のある開発に参画したので、
その時に「ここが難しかったなあ」というところを自分なりに図解してみたいと思います。
前提
- ReactとReduxの接続にはreact-reduxのconnectを使うこととします。
この記事で伝えたいところ
自分が難しく感じたのはこのあたりでした。
- (ソースが色々あって)全体の流れがよくわからない
- actionを起こすとreducerが反応する仕組みがよくわからない
- mapStateToPropsやmapDispatchToPropsがおまじないにしか見えない
読み終えたときにこれらが理解できることを目指して書いていきたいと思います。
なんとなく「こうすれば一通り動くのはわかるけど理由が知りたい」人向けです。
Reduxだけの場合
まずReactを省略してReduxだけの概念を図解します。
Reduxとは、アプリケーションの状態(State)管理を容易にするためのフレームワークです。
次の図を見ると矢印の向きが1方向になっているのがわかります。
このように単一方向の状態管理を実装するためのStoreという層を提供してくれます。
全体の流れは以下です。(図中の数字と対応付けてます)
- View(画面)のInputをもとに、Action Creator(*)を使って
action
を生成する。 - Storeに対してactionを
Dispatch
する。 - ReducerがDispatchに反応して、actionをもとに
State
を更新する。 - ViewはStoreから
State
を参照する。(getStateと言う取得関数があります。)
これの繰り返しで状態を更新・管理していきます。
(*) actionはViewで直接生成したりしてもよいのですが、大抵Action Creatorがactions
等のjsにまとめて定義されます。こんなやつです。
// create addTodo action
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
(このaction creatorとactionがごっちゃになりがちなのも個人的に最初難しく感じるところでした)
図中のMiddlewareについては今は触れないでおきます。
- Stateの更新はReducerが行う。
- Reducerに働いてもらうためには、ActionをStoreにDispatchする。
- Stateを取得するには、StoreからgetStateする。
という点を抑えておきます。
React-Reduxの場合
次に、実際にReduxをReactで使う場合です。
ReactとReduxを繋ぐContainer
ReactとReduxを繋ぐComponentをContainer Componentと呼びます。
以下のようなイメージになります。
- Container Componentは
this.props.anyAction()
のようにしてactionを発行できるようになります。 - 同様に、
this.props.anyState
のようにしてStateを参照できるようになります。
前後しますが、コード例としては以下のようになります。
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'
class MyContainer extends Component {
// 省略
}
const mapStateToProps = (state, ownProps) => {
return {
todos: state.todos
}
}
const mapDispatchToProps = { // あえて関数ではなくオブジェクトにしておきます。
addTodo
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(MyContainer)
この時のmapStateToPropsとmapDispatchToPropsを
理解できていればreact-reduxについてはバッチリだと思います。
mapStateToPropsとmapDispatchToProps
「Reduxだけの場合」に記載した以下のポイントを思い出しましょう。
- Reducerに働いてもらうためには、ActionをStoreにDispatchする。
- Stateを取得するには、StoreからgetStateする。
connect(mapStateToProps, mapDispatchToProps)
は、
ReduxのStore
をReact Componentの世界に持ち込んでくれます。
以下のようなイメージです。
- mapStateToPropsは、Store.getState()のような役割をして、ComponentのpropsにStateの中身を詰め込んでくれます。
- mapDispatchToPropsは、ActionCreatorをラップした、
actionをStoreにDispatchしてくれる関数
をpropsに詰め込んでくれます。
特に2点目はたまに理解できてない場合があります。
たとえば、上の方にあるコードで、importしているaddTodo()をpropsを通さず直接実行してもReducerは反応しません。
それは、単純にActionCreatorだけを実行しても、Dispatchしたことにならないからです。
mapDispatchToPropsは関数でも書くことができて、そちらを見るとわかりやすいです。
(自分が今まで見たコードではいつも単純なオブジェクトでした。。)
const mapDispatchToProps = dispatch => ({
// actionCreatorで生成したactionをdispatchする関数を作っている。
// これがMyContainerに渡すaddTodoになる。
addTodo: text => dispatch(addTodo(text))
})
export default connect(
mapStateToProps,
mapDispatchToProps
)(MyContainer)
このような形で、Storeへのアクセスを私たちが意識しなくていいように
React Componentへ、文字通り繋いでくれるのがconnect(mapStateToProps, mapDispatchToProps)
関数になります。
※connect関連のapiには、例には書いてない引数や機能もあるので興味ある方はreact-reduxのGitHubを読んでみてください!
そして、すべてのReact ComponentをContainer化してしまうと
Reduxへ依存し過ぎてしまい再利用性のあるComponentが書けなくなってしまうため、
大抵以下のようにpropsを使って、子のComponentへ関数や値を渡して利用します。
Provider
でも、ここまでのコード例で一度もStore
なんて出てきてないのに
どうやってStoreを利用できるの?と思うかもしれません。そういった疑問を持てるのは素敵です。
react-reduxからのほぼ引用になりますが、下記のようにProvider
を使うことで
ラップされたComponent達がStoreとconnectできるようになります。
// 主旨じゃないimportとかは省略してます
import { Provider } from 'react-redux'
const store = createStore(myRootReducer)
ReactDOM.render(
<Provider store={store}>
<MyRootComponent/>
</Provider>,
document.getElementById('root')
)
ここまで知っていればReduxをReactで使う方法はほぼ全てわかったと言って良いと思います。お疲れさまでした。
実際にはreact-routerと一緒に使うことも多いと思います。real-world exampleが参考になります。
Middlewareについて
図中で放置されていたMiddlewareについてもさっと触れておきます。
Storeについて理解できていればあまり難しいことはありません。
- actionがReducerにたどり着く前に、actionを見て処理を実行できる場所
- Middlewareは、Storeにアクセスできる
この2点だけ抑えておけば大丈夫だと思います。
Reducerへ辿りつく前に、その場でactionを捨てたり、作り直したり、関心がない場合は何もしないこともできます。
Storeにアクセスできるので、今のStateを参照したり、その場で新たなactionをDispatchしたりできます。
例えば、react-redux公式のreal-world exampleでは、
シンボル化されたCALL_API
actionを拾って、API実行の結果に応じて
request, success, failureに分けてactionをReducerへ流すようにしています。
終わりに
繰り返しになりますが、
- Reducerに働いてもらうためには、ActionをStoreにDispatchする。
- Stateを取得するには、StoreからgetStateする。
この点が理解できていればReduxを難しいと感じなくなるのかなと思います。
例えば、redux-thunkというライブラリがありますが、これもaction creatorの中で
Storeのdispatch、getStateができるだけと考えれば難しくありません。
うまく説明できたかわかりませんが、これからReact/Reduxの世界へ飛び込む人の助けになればと思います。
(そして将来また人に教えるときの自分の助けにもなるといいな。。。と思って書きました。)
以上です。