【React】ReduxのState/Store/Action/Reducer設定まとめ
Vue.jsを使用している際にもVuexやVue Routerなどで、
Stateを全てのコンポーネントに渡すことができるという機能がありましたが、
そのReact版がReact Reduxです。
大まかに、React Reduxを使ったState/Store/Actionの設定まとめをしていきます。
React Reduxの必要性
Reactを使って多重階層のコンポーネントを用いる際、Storeという保管庫からどのコンポーネントからでも簡単にデータを取り出すことができるため便利。
従来のState管理では渡したいデータがあった場合、親コンポーネントから子コンポーネントへ、そして子コンポーネントから孫コンポーネントへ…という煩雑な手間がありました。
下準備
Reactプロジェクト内で、reduxとreact-reduxという2つのパッケージをインストールする。
$ yarn add redux react-redux
また以下のような階層でState/Store/Actionを区分けしておくと便利
📦src
┣ 📂actions
┃ ┗ 📜index.js
┣ 📂components
┃ ┗ 📜App.js
┣ 📂reducers
┃ ┣ 📜count.js
┃ ┗ 📜index.js
┣ 📜index.css
┣ 📜index.js
┗ 📜serviceWorker.js
Action
全てのコンポーネントで共通したアクションを登録しておくためのファイル。
またActionのファイル内で、アクションを定義する関数のことをAction Createrという。
以下は一例
src/actions/index.js
// 複数箇所でINCREMENTなどは使用されるため変数化しておく export const INCREMENT = 'INCREMENT' export const DECREMENT = 'INCREMENT' // 下記のINCREMENTとDECREMENTでは書き方が違うが、 // 上はリファクタリングが行われているだけで意味は同じ // またactionを定義してreturnする関数をアクションクリエイターという export const increment = () => ({ type: INCREMENT }) export const decrement = () => { return { type: DECREMENT } }
Reducer
アクションが発生した際に、そのアクションに組み込まれているタイプによってどのようにStateを変化させるのかを設定しておくのがReducerです。
全てのReducerをまとめるためのsrc/reducers/index.jsと、個別のReducerであるsrc/reducers/count.jsの2つを用意しました。
src/reducers/index.js
// 全てのReducerをコンバイン(合体)させるためのもの import { combineReducers } from 'redux' import count from './count' export default combineReducers({ count })
実際のReducerの役割は以下のように個別のReducerファイルに分ける
src/reducers/count.js
import { INCREMENT, DECREMENT } from '../actions' const initialState = { value: 0 } export default (state = initialState, action) => { // eslint-disable-next-line default-case switch(action.type) { case INCREMENT: return { value: state.value + 1 } case DECREMENT: return { value: state.value - 1 } default: return state } }
Store
Storeは、アプリ内にある全てのStateを管理するための保管庫というイメージです。
Storeを設定することによってどのコンポーネントからでも値の受け渡しをせずにStateを取り出すことができるようになります。
src/index.js
import React from 'react'; import ReactDOM from 'react-dom'; // 以下2つをインポート import { createStore } from 'redux'; import { Provider } from 'react-redux'; import './index.css'; // 上記で作成したReducerをインポート import reducer from './reducers'; import App from './components/App'; import * as serviceWorker from './serviceWorker'; // アプリ内唯一のstoreを定義。 // またreducerを引数に入れることで // 全てのアプリケーションのStateがStoreに格納される。 // reducerのcount.js内にあるinitialState = { value: 0 }が渡される。 const store = createStore(reducer); ReactDOM.render( // Providerタグで囲い、store属性に定義したstoreを格納することで、 // 全てのコンポーネントからStore内に格納されたStateを参照できるようになる <Provider store={store}> <App /> </Provider>, document.getElementById('root') ); // If you want your app to work offline and load faster, you can change // unregister() to register() below. Note this comes with some pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister();
StateとActionを関連づけるConnect
Stateを管理するためのStore、Actionを設定するためのActionとAction Creater、
そして操作によって適切なActionを呼び出すためのReducersを設定しました。
最後にActionによって適切にStateに処理が反映させるためにconnectを導入します。
src/components/App.js
import React, { Component } from 'react'; import { connect } from 'react-redux'; import { increment, decrement } from '../actions'; class App extends Component { render() { // stateやactionを格納するためのprops const props = this.props return ( <div> {/* reducerのcount.js内にあるvalue(initialState)を表示する */} { props.value } <button onClick={props.increment}>+1</button> <button onClick={props.decrement}>-1</button> </div>) } } // mapStateToPropsはStateの情報からコンポーネントで必要な情報を取り出して、 // コンポーネント内のpropsとしてマッピングする機能を持つ // 引数にはstateを書いて、どういったオブジェクトをpropsとして対応させるのかを定義させる const mapStateToProps = state => ({ value: state.count.value }) // mapDispatchToPropsはあるアクションが発生した時にReducerにTypeに応じた状態遷移を実行させるための関数 // incrementのためのボタンとdecrementのためのボタンに必要なアクションをdispatch関数の引数に渡す const mapDispatchToProps = dispatch => ({ increment: () => dispatch(increment()), decrement: () => dispatch(decrement()) }) export default connect(mapStateToProps, mapDispatchToProps)(App);
これでどのコンポーネントでも同じAction、Stateを使用できるようになりました。
ちなみにmapDispatchToPropsには省略記法があり、以下のように書くことができます。
const mapDispatchToProps = ({ increment, decrement })
たった1行だけですが、処理的に違いはありません。