概要
最近React+Reduxについて触れる機会があったのでちょっとやってみたら、Reduxの処理がわからなかったので自分の理解用にまとめてみました。
sample codeの準備と説明
今回はsample codeを使用して説明していきたいと思います。
githubにcodeと実行手順を記載しているのでここでは割愛します。
まず、このsample codeはどのようなものか
構成は下記になります。
$ tree .
├── package.json
├── public
│ └── index.html
└── src
├── actions
│ └── action.js
├── components
│ └── App.js
├── containers
│ └── IOString.js
├── index.js
└── reducers
└── index.js
ここのpublic/index.html
とsrc/index.js
はreact-scriptsが起動したタイミングで一番最初に呼ばれるファイルになります。
Reduxの処理の流れ
初回読み込み
react-scriptを実行したら最初にpublic/index.html
とsrc/index.js
が呼ばれます。
public/index.html
は最終的にjs側で生成したhtmlを貼り付けるだけなので飛ばします。
src/index.js
では最初の読み込みが行われます。
これはimport
を書いた順に実行されていきます。
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import App from './containers/IOString'
import reducer from './reducers'
const store = createStore(reducer)
...
その後createStore
が実行されて./reducers
配下のindex.jsファイルが呼び出されます。
今回は1つしかないので./reducers/index.js
が呼ばれます。
自分のイメージでは、ここで呼ばれた処理は今後のアクションによって条件分岐していくものなのであらかじめ呼ばせて準備しておくイメージです。
reducers
export default function reducer(state, action){
switch(action.type){
case 'ADD':
return {no: action.no, text: action.text}
default:
return {no: 0, text: "start"}
}
}
今回はaction.typeがADDの時かそれ以外(default)の場合の2択になっています。
ちなみに初期値はdefaultの{no: 0, text: "start"}
になります。
そのあと、src/index.js
のProviderにstoreをセットします。
containers
次にsrc/index.js
のAppが呼び出される。
Appの内容は下記になります。
import React from 'react'
import { connect } from 'react-redux'
import { ioData } from '../actions/action'
import App from '../components/App'
const mapStateToProps = state => {
return state
}
const mapDispatchToProps = dispatch => {
return {
onClickAdd: text => {dispatch(ioData(text))}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
mapStateToProps
はconnect
でReact component と Redux storeをつなげます。
connect
はReactコンポーネントをReduxストアに接続する役割です。
components
import React from 'react'
let input
export default class App extends React.Component {
render () {
return (
<div>
<form onSubmit={e => {
e.preventDefault()
if(!input.value.trim()) {
return
}
this.props.onClickAdd(input.value)
input.value = ""
}}>
<input ref={node =>{
input = node
}} />
<button>
add string
</button>
</form>
<span>{this.props.no}:{this.props.text}</span>
</div>
)
}
}
ここのHTMLは最終的にsrc/index.js
のAPPの箇所に出力されます。
今回は下から説明します。
・・・
<span>{this.props.no}:{this.props.text}</span>
・・・
ここで宣言されているthis.props群ですが画面で数値と画面で入力した値が入ってきます。
このprops.noとtextはsrc/containers/IOString.js
で設定されていたmapStateToProps
のstateが対象になります。
(おそらくindex.jsで設定されたcreateStoreの引数がmapStateToPropsを通ってきたのかなと思います。)
その上のinputでは、入力した値をinput変数に格納し
formのonSubmitでbuttonが押下されたらinputの内容をonClickAddで飛ばして値を空にしています。
この時onClickAddはsrc/containers/IOString.js
のmapDispatchToPropsの値が対象になります。
ここでsrc/actions/action.js
に処理を渡し戻り値をdispatchがstoreに通知します。
action
let noCount = 1
export const ioData = (text) => ({
type: 'ADD',
no: noCount++,
text
})
ここではstoreにデータを送信するための情報を作成します。
先ほどsrc/components/App.js
で呼ばれたonClickAddは一旦src/containers/IOString.js
のmapDispatchToPropsに行き、そこに設定してあったactionに移動します。
ここのioData = (text)
のtextにはinput変数の値が入ります。
またonCountにはlet noCount = 1
で初期値を設定していて、その値のカウントを行なっています。
(リロードしない限りカウントは消えません)
ここで設定された値はstoreを通してcreateStoreで設定されたreduceに向かいます。
そこのreturnに設定していある値をsrc/components/App.js
の
・・・
<span>{this.props.no}:{this.props.text}</span>
・・・
で出力しているという流れでした。
まとめ
実際に見ただけではわかりづらく、実際に動かして処理の流れを追わないと理解するのは難しいかなと思いました。
今回はざっと書いて見ましたが、部分ごとに深堀したほうがいいところがあると思うので次reduxを書く機会があったら深堀してみようと思います。
sample code
https://github.com/takahiron/sample-redux