React入門 Redux
React入門本を読んで、Reduxについて勉強した。
React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで (NEXT ONE)
- 作者: 穴井宏幸,石井直矢,柴田和祈,三宮肇
- 出版社/メーカー: 翔泳社
- 発売日: 2018/02/19
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
基本
- Store
- stateの唯一の保管先。「ここさえ見ればアプリの状態がすべてわかる」。
- Reducer
- stateの変更方法を定義するもの。
- Action
- Reducerに対してstateの変更をリクエストするもの。
store
createStore関数にReducerを指定して、storeオブジェクトを得る。
import { createStore } from 'react-redux'; const store = createStore(myReducer);
storeオブジェクトは以下のメソッドを持つ。
- dispatch(action)
- 与えられたActionに応じ、Reducerに定義された方法でstateを変更する。
- subscribe(function)
- dispatchのタイミングで発火するイベント関数を設定する
- getState()
- storeに保管されているstateを得る
- replaceReducer(reducer)
- storeオブジェクトに関連付けられたReducerを他のReducerに置き換える
- なお、Reducerは複数定義しておいてcombineReducers関数でひとまとめにすることもできる。replaceReducerは動的にReducerを置き換えたい場合に使う。(らしい)
combineReducersは、大きなアプリなどでReducerを分割して開発するほうが効率が良い場合に使う。小さいアプリであれば1つのReducer内で分岐で処理しても問題ない。
Reducer
実体は関数。 storeのdispatchでactionが渡されたときに、その時のstateと渡されたactionを引数にとり、 actionに応じた処理を行う。
以下、一例。
function myReducer(state = initialState, action){ switch(action.type){ case 'MY_ACTION_1': return { ...state, hoge: action.payload.fuga, }; default: return state; } }
Action
Actionを作る関数をActionCreaterという。
後述のFSA(Flux Standard Action)に則ったオブジェクトを返すことが推奨される。
以下、一例。
const myAction1 = () => ({ type: "MY_ACTION_1", payload: { fuga: "hello", } })
myAction1をmyReducerにdispatchすると、stateに{ hoge: "hello" }
が書き込まれる結果となる。
store.dispatch(myAction1());
Reactアプリでの使い方 without react-redux
とりあえず、storeオブジェクト経由でstateの管理を自由にできるので、stateに対して読み書きしたいコンポーネントのpropsにstoreを渡せばよい。
その場合、setStateは使わず、アプリをrenderする関数を作っておき、store.subscribeに登録することで、store更新があったときに再描画が走るようにする。
でもそれだとアプリが大きくなってきたときに、
- 必要なコンポーネントだけを再描画する処理を書くのが大変
- 階層的なコンポーネントにstoreオブジェクトをたらい回しで渡していくのは美しくない
- コンポーネントがreduxに密結合してしまい、再利用性が損なわれる
といった問題があるので、react-reduxを使うとよい。
Reactアプリでの使い方 with react-redux
react-reduxでは、ReduxのstoreとReactのComponentを、connectという関数を介してつなげることで、疎結合なつくりにする。
主な要素は二つ。
Provider
storeを各コンポーネントのcontextを通じてアクセスできるようにするコンポーネント。
自分はまだcontextの仕組みはよくわかっていないが、現状おまじない的にアプリの大元のコンポーネントをProviderで囲むようにする。
index.jsはこんな感じになる。
import React from 'react'; import { render } from 'react-dom'; import { Provider } from 'react-redux'; import { createStore } from 'redux'; import rootReducer from './reducers'; import MyApp from './components/MyApp '; const store = createStore(rootReducer); render( ( <Provider store={store} > <MyApp /> </Provider> ), document.getElementById("root") );
Container component
connect関数を使って作られるコンポーネント。 connect関数では、引数に渡す関数を通してコンポーネントのpropsにstateやactionへのアクセス用オブジェクトを渡す。
import { connect } from 'react-redux'; import { myMethod } from '../actions'; import MyAppfrom "../components/MyApp"; // mapStateToPropsでは、MyAppに渡したいオブジェクトを作ってreturn function mapStateToProps(state) { return { ・・・ }; } // mapDispatchToPropsでは、MyAPpに渡したいactionをラップした関数をオブジェクトにしてreturn function mapDispatchToProps(dispatch) { return { myMethod(value) { dispatch(myMethod(value)); }, ・・・ }; } export default connect(mapStateToProps, mapDispatchToProps)(MyApp);
connectの第三引数にmergeProps関数も指定できる。
- mergeProps
- state, dispatchおよびコンポーネント自身のpropsをマージするロジックを書いた関数を渡す。
- デフォルトではmapStateToPropsとmapDispatchToPropsの返り値オブジェクトを単にマージしたオブジェクトを返すようになっている。
基本的には、mapStateToPropsでそのコンポーネントに必要なstateを入れたオブジェクトを返し、
mapDIspatchToPropsでそのコンポーネントに必要なActionを定義した関数が入ったオブジェクトを返し、
mergePropsはデフォルトで使えばよい。
受け取り側のコンポーネントでは、mapStateToPropsとmapDispatchToPropsで返したstate/actionはprops.***で利用できる。
connect関数を書くファイルはContainer Componentと呼ばれる。 このようなコンポーネントを間に挟むことで、Reactのコンポーネントは純粋にViewの処理(+コンポーネントに閉じたstatefulな処理)のみを記述できる。
※対してReactのコンポーネントはPresentational Componentという。
index.jsはこうなる。
import React from 'react'; import { render } from 'react-dom'; import { createStore } from 'redux'; import { Provider } from 'react-redux'; import rootReducer from './reducers/reducers'; import App from './containers/App'; const store = createStore(rootReducer); render( ( <Provider store={store}> <App /> </Provider> ), document.getElementById('root') );
ためしに実践
Qiita記事検索アプリ
例によってQiita記事をキーワード検索するだけのアプリを作ってみた。
某RPG風ショップアプリ
前から作ってみたかったお遊びアプリ。
設計について
設計順序とポイントとして、現状は以下がいいかなと思う。
何度か使っていけばもっといい方法が身についていくと思う。
- アプリに必要そうなstateを一通りreducerにinitialStateとして定義する。
- ルート画面のコンポーネントを作り、必要なコンポーネントを洗い出す。
- 各コンポーネントをできる限りStatelessに作る。
- initialStateと各コンポーネントのpropsとactionを確認し、stateの過不足を検討してきれいにする。
- 必要なreducerを定義する。
- reducerはstateごとに作る。
- なるべく単純な操作を行うよう定義する。
- 必要なActionを定義する。
- まずは、reducerが管理するstateに対して単純な操作をするactionとして定義する。
- その後、複数のactionを組み合わせたり非同期処理を入れたりして、UIから直接使えるActionCreaterを定義する。
(そのためには、本で紹介されているMiddlewareのredux-thunkをつかう)
- Container Componentを作る。
発展
以下の記事にて、reducerやactionをどう設計するかについて知見が紹介されている。
- Reducer
- Redux公式ドキュメントで、DomainState、 AppState、 UIState という3つのStateに分割することが提案されているとのこと。(Componentごとに分けるのではなく。)
- Action
- FSA(Flux Standard Action)を使うとよい、とのこと。
- Actionの定義が人によってまちまちになりがちなので標準化したもの。