初心者によるReduxのふわっとしたイメージ解説
Redux + React に挑戦している。 なんとなく感覚はつかめてきたが小さい謎が多い。 感覚をつかむまでの苦しみがすごいのでメモを残しておきたい。
実際の Redux のライフサイクル
イメージ解説:「りんごがひとつ売れたので在庫情報を更新したい」
ユーザーさん(View):「りんごがひとつ売れたので在庫情報を更新したい」
受付さん(Action Creator):「わかりました。少し待っててくださいね。φ(・_・”)」
受付さん(Action Creator):「お待たせしました。これを事務さんに持っていってください。」
依頼書(Action):「【業務】りんごが売れたため在庫更新【個数】1」
事務さん(Reducer):「りんごが売れたため在庫更新、個数は1、ですね。わかりました。」
事務さん(Reducer):「書類棚から在庫管理表を取ってきて、」
- 書類棚(Store)
- 在庫管理表(State)
事務さん(Reducer):「新しい在庫管理表と依頼書を照らし合わせて新しい在庫管理表を作る。」
事務さん(Reducer):「それでは在庫更新しておきましたのでもう大丈夫ですよ。」
ユーザーさん(View):「ありがとうございました。」
テスト用プロジェクト
ROOT_DIRECTORY/ + node_modules/ + build/ + source/ | + renderer/ | | + components/ | | + app/ | | + container.js | | + view.js | | + reducer.js | | + actions.js | | | + index.html | + gulpfile.babel.js + .babelrc + package.json
source/index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Redux Sample</title> </head> <body> <div id="app"></div> <script src="./renderer/bundle.js"></script> </body> </html>
source/renderer/components/app/container.js
connect
は Redux と React をつなげる。
mapStateToProps
は Redux で管理する state を React コンポーネントの props にひもづける。
mapDispatchToProps
は Action Creator への呼び出しを React コンポーネントの props にひもづける。
React コンポーネントでは state ではなく props を使っていくことになる。
自分でも書いててよく分からんけど何回か書いてたら慣れる。
import React from "react"; import { connect } from "react-redux"; import Component from "./view"; import Actions from "./actions"; export default connect( mapStateToProps, mapDispatchToProps )(Component); function mapStateToProps(state) { console.log("container.js: mapStateToProps\n" + "state: " + JSON.stringify(state, null, " ")); return state.app; } function mapDispatchToProps(dispatch) { console.log("container.js: mapDispatchToProps"); return { "sellHandler": () => { dispatch(Actions.sell()); }, "recieveHandler": () => { dispatch(Actions.recieve()); } }; }
source/renderer/components/app/view.js
React のコンポーネント。
import React from "react"; export default class Component extends React.Component { render() { console.log("view.js: render\n" + "state: " + JSON.stringify(this.state, null, " ") + "\n" + "props: " + JSON.stringify(this.props, null, " ")); return ( <div> <table> <thead> <tr> <th>品名</th> <th>単価</th> <th>在庫</th> </tr> </thead> <tbody> <tr> <td>{this.props.product.name}</td> <td>{this.props.product.price}</td> <td>{this.props.product.stock}</td> </tr> </tbody> </table> <button onClick={this.props.sellHandler}>売る</button> <button onClick={this.props.recieveHandler}>入荷する</button> </div> ); } }
source/renderer/components/app/reducer.js
Action による state の変更を管理する。state の初期値もここで設定する。事務さん。
const initialState = { "product": { "name": "りんご", "price": 120, "stock": 100 } }; export default function reducer(state = initialState, action) { console.log("reducer.js: reducer\n" + "state: " + JSON.stringify(state, null, " ") + "\n" + "action: " + JSON.stringify(action, null, " ")); switch(action.type) { case "PRODUCT_SOLD": return Object.assign({}, state, { "product": { "name": state.product.name, "price": state.product.price - action.amount, "stock": state.product.stock } }); case "PRODUCT_RECIEVED": return Object.assign({}, state, { "product": { "name": state.product.name, "price": state.product.price + action.amount, "stock": state.product.stock } }); default: return state; } }
source/renderer/components/app/actions.js
Action を返し、state の変更パターンを定義する。受付さん。
export default { "sell": () => { console.log("actions.js: sell"); return { "type": "PRODUCT_SOLD", "amount": 1 }; }, "recieve": () => { console.log("actions.js: recieve"); return { "type": "PRODUCT_RECIEVED", "amount": 1 } } }
ログつきテストページ
デバッガーのコンソールタブにログがでる。
ページ読み込み時のログ
初期描画時に謎に Reducer が 3回も動いている。謎でしかない。
reducer.js: reducer state: { "product": { "name": "りんご", "price": 120, "stock": 100 } } action: { "type": "@@redux/INIT" } reducer.js: reducer state: { "product": { "name": "りんご", "price": 120, "stock": 100 } } action: { "type": "@@redux/PROBE_UNKNOWN_ACTION_v.1.7.7.f.r" } reducer.js: reducer state: { "product": { "name": "りんご", "price": 120, "stock": 100 } } action: { "type": "@@redux/INIT" } container.js: mapStateToProps state: { "app": { "product": { "name": "りんご", "price": 120, "stock": 100 } } } container.js: mapDispatchToProps view.js: render state: null props: { "product": { "name": "りんご", "price": 120, "stock": 100 } }
「売る」ボタンを押したときのログ
Action Creator => Reducer の流れで state
の値が変化していること、 state
変化後に render
が呼ばれていることがわかる。
stock
を変えたつもりがprice
が変わっている。凡ミス。mapStateToProps
って毎回呼ばれるのね。props
にひもづけたらお役御免かと思っていた。
actions.js: sell reducer.js: reducer state: { "product": { "name": "りんご", "price": 120, "stock": 100 } } action: { "type": "PRODUCT_SOLD", "amount": 1 } container.js: mapStateToProps state: { "app": { "product": { "name": "りんご", "price": 119, "stock": 100 } } } view.js: render state: null props: { "product": { "name": "りんご", "price": 119, "stock": 100 } }
「入荷する」ボタンを押したときのログ
Action Creator => Reducer の流れで state
の値が変化していること、 state
変化後に render
が呼ばれていることがわかる。
actions.js: recieve reducer.js: reducer state: { "product": { "name": "りんご", "price": 120, "stock": 100 } } action: { "type": "PRODUCT_RECIEVED", "amount": 1 } container.js: mapStateToProps state: { "app": { "product": { "name": "りんご", "price": 121, "stock": 100 } } } view.js: render state: null props: { "product": { "name": "りんご", "price": 121, "stock": 100 } }