#目次
#概要
この記事では、状態管理を行うためのフレームワークであるReduxの基礎や状態管理の仕組みについてまとめています。現在Reduxについて勉強中の方の参考になるようでしたら幸いです。
なお本記事はReactを用いていることを前提条件としています。
(注意)
またReact ComponentとRedux Storeを関連づける手法として、現在はHooksとReduxを用いた手法もありますが、今回は従来のconnect関数を用いた手法で紹介しています。後日Hooksを用いた手法についても投稿予定です。
#Reduxとは
Reduxとは、上記でも述べたようにReactの状態(state)を管理するフレームワークです。
またReduxはReactと併用することを想定して生み出されているため、Reactと非常に相性が良いとされています。
ReduxはFluxアーキテクチャの一つで、コンポーネントの数が多くなったときに簡単にstateを共有するための手段として利用されています。
またReduxなどのFluxアーキテクチャの最大の特長はデータフローが単方向で構築できることで、規模が大きくなった場合にもデータの流れを見失いにくくなります。(後ほど図解)
Reduxを使用するためには以下のコマンドでインストールしておく必要があります。
$ yarn add redux react-redux
#Reduxの要素
Reduxによる状態管理を行うための構成要素には主に以下の4つが必要となります。
- Action:アプリケーション内でなにが起きたのかを示すオブジェクト(データ)
- Reducer:Actionのtype(種類)に応じてstateを変化させるメソッド
- Store:アプリケーション内の全てのstateを保持している場所
- State:アプリケーションの状態
#Reduxのデータフロー
上記のような構成にすることで、先ほど述べたデータフローの単方向化を実現することが可能にすることができます。
- ActionCreatorによってActionを生成
- Actionをdispatch
- ReducerでStore内のStateを更新
- Store内のStateをReact Componentで参照(ReactとReduxの連携)
では次にAction、Reducer、Store、ReactとReduxの連携方法について詳しく説明します。
(stateについては省略します)
#Action
Actionの特徴は以下のことが挙げられます。
- アプリケーションの中でなにが起きたかを示すオブジェクト(データ)である
-
type
とそれに対応する値を持つ(typeの値はユニークなものにする) - Storeの唯一の情報源
- ActionCreatorによって生成される
###ActionCreator
Actionを作成するメソッド
FluxにもActionCreatorがありますが、ReduxではActionを作成するのみでStoreへのdispatchは行わないという違いがあります。
例えばToDoリストアプリケーションでリストを追加したいときにはActionCreatorと組み合わせて次のように記述します。
const ADD_TODO = 'ADD_TODO';
// Action Creator
const addTodo = text => {
return {
// Action
type: ADD_TODO,
text
}
}
前述の通り、ActionCreatorによって生成されたActionは、生成されたのみでStoreへdispatchされていません。
Reduxではdispatch()
メソッドによってActionをStoreに送ることができます。
dispatch(addTodo(text));
#Reducer
Reducerの特徴は以下のことが挙げられます。
- Actionの
type
に応じて状態をどう変化させるのかを定義したメソッドである - 引数のstateを更新するのではなく、新しいStateを作成して返す
- 純粋関数でないとならない(毎回必ず同じ結果を返す)
Reducerの実装手順は以下のようになります。
- 状態(state)はオブジェクトとして初期値を定義
- Reducerは関数として定義、引数は2つ(state, action)
- Actionのtypeに応じて状態を変化させ結果を返す
コードで見ると以下のようなイメージになります。
import { ADD_TODO, REMOVE_TODO } from '../actions';
const initialState = { text: 'initial text' };
export default (state = initialState, action) => {
switch(action.type) {
case ADD_TODO:
return {
// ActionがADD_TODOの場合のState更新処理
};
case REMOVE_ADD:
return {
// ActionがREMOVE_TODOの場合のState更新処理
};
default:
return state; // Actionのtypeに当てはまらない場合はデフォルトを返す
}
};
ToDoリストとして正しいinitialStateの設定かは分かりませんが、Reducerの実装イメージとしてはこんな感じになります。
またReduxではアプリケーション内に存在する全てのReducerを1つのReducerとして結合することできます。
import { combineReducers } from 'redux'; // このimportが必要
import reducer1 from './reducer1';
import reducer2 from './reducer2';
export default combineReducers({ reducer1, reducer2 });
#Store
Storeの特徴は以下のことが挙げられます。
- Storeはアプリケーション内で唯一のもの
- アプリケーション内のStateが全て集約されている
- 従来のReactではpropsを目的のコンポーネントまでバケツリレー形式で渡していたが、
<Provider>
によりその必要がなくなる(react-reduxからインポート)
Storeは以下のように作成します。
import {createStore} from 'redux';
import reducer from './reducers';
const store = createStore(reducer);
#connect関数
ReactとReduxは互いに独立しているため、なにもしないままだとReact ComponentをReduxのフローに乗せることができません。そこでReact Reduxが提供しているconnect()
関数を利用して、ReduxのStateやActionとReact Componentを接続します。このとき接続されたComponentはContainerと呼ばれることもあります。
このようにconnect()
関数によってContainerではStoreから必要なデータ(Stateの一部)と、ActionをStoreに渡すためのdispatch()
関数を使用することができます。
基本的な記述方法としては以下のように書きます。
connect(mapStateToProps, mapDispatchToProps)(App);
ここでconnect()
関数の引数に渡されているmapStateToProps
とmapDispatchToProps
について詳しく見ていきましょう。
###mapStateToProps
StoreにあるStateの情報からContainerで必要な情報を取り出し、Container内のpropsとしてマッピングする機能を持つ関数です。mapState
として呼ばれることもあります。
上記でも示したようにconnect()
関数の第1引数になります。
呼ばれるタイミング | Storeの状態が変化したとき |
第1引数 | State(Storeの全ての状態) |
第2引数(オプション) | 自身のprops(ownProps) |
戻り値 | Containerが必要としているStateをpropsとして返す |
const mapStateToProps = state => {
return {
// 全体のStateから必要な情報 (state.valueなど)
// value: state.value
// のように記述
};
};
ownPropsを第2引数に設定した場合、最終的な戻り値にはownPropsと新たに得たデータが1つのpropsとしてマージされます。
###mapDispatchToProps
ActionをStoreにdispatchするdispatch()
関数をpropsにマッピングするために使用されます。mapDispatch
として呼ばれることもあります。
dispatch()
関数とは、あるActionが発生したときにReducerにtype
に応じた状態遷移を実行させるための関数です。Storeに標準でdispatch()
関数が用意されています。
mapDispatchToProps
をconnect()
関数の引数に指定しなくてもコンポーネントはデフォルトでprops.dispatch
を受け取れるので、これを使用してStoreにActionをdispatchすることができます。
ではmapDispatchToProps
を使用する理由はなんでしょうか?
例として、「ボタンを押すとvalue
が+1される」というContainerを見てみましょう。
mapDispatchToProps
を使用しない場合
Container側でActionCreatorによってActionを作成した段階ではdispatchされていないので以下のように記述する必要があります。
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { increment } from '../actions';
class Counter extends Component {
render(){
const props = this.props;
return (
<React.Fragment>
<div>count: {props.value}</div>
<button onClick={() => dispatch(increment())}>+1</button> {// ここ }
</React.Fragment>
);
}
}
const mapStateToProps = state => ({ value: state.count.value});
export default connect(mapStateToProps /*, 第2引数なし(mapDispatchToProps) */)(Counter);
若干JSXの記述が複雑になっています。
mapDispatchToProps
を使用した場合
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { increment } from '../actions';
class Counter extends Component {
render(){
const props = this.props;
return (
<React.Fragment>
<div>value: {props.value}</div>
<button onClick={props.increment}>+1</button>
</React.Fragment>
);
}
}
const mapStateToProps = state => ({ value: state.count.value});
const mapDispatchToProps = ({ increment });
// 上記は下の記述と同じ意味
// const mapDispatchToProps = dispatch => ({
// increment: () => dispatch(increment())
// });
export default connect(mapStateToProps, mapDispatchToProps)(App);
このように記述することができます。
mapDispatchToProps
内ではActionCreatorでActionを生成しdispatchまで行う処理を関数に定義してpropsに渡しています。
今回は処理が少ない場合でしたのであまり影響はありませんが、Actionの種類や作成の数が多くなった場合にはmapDispatchToProps
を使用することでContainer側で毎回dispatch
処理を記述する必要がなくなります。
呼ばれるタイミング | ActionCreatorが呼び出されたとき (今回はクリックされたとき) |
第1引数 | dispatch()関数 |
第2引数(オプション) | 自身のprops(ownProps) |
戻り値 | 指定したActionのdispatch処理をpropsとして返す |
第2引数としてownPropsを設定した場合、Containerが新しいpropsを受け取ったタイミングでmapDispatchProps
が呼び出されます。
#Provider
Component階層の最上位のComponentを<Provider>
でネストすることで全体のComponentをStoreに接続することが可能になります。
それによって任意のComponentでconnect()
関数を用いてStoreと接続できます。
コードの記述イメージは以下のようになります。
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { App } from './App';
import createStore from './createReduxStore';
const store = createStore();
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
#まとめ
長くなりましたが、Reduxの基礎については以上にしたいと思います。
では簡単に今回の記事の内容をまとめます。
- Redux:アプリケーションの状態管理を行うためのフレームワーク
- Action:アプリケーションでなにが起きたのかを示すオブジェクトデータ
- Reducer:Actionの種類に応じて状態を変化させるメソッド
- Store:アプリケーションの全ての状態を保持している場所
- connect():React ComponentとReduxを接続するためのメソッド
- Provider:connect()でComponentをStoreに接続するために必要なもの
各要素の役割とデータフローをしっかり抑えておくことが重要です。
Reduxについては個人的に理解するのがなかなか大変で、まだ完全には理解できていない面もあるので今後も勉強していきたいと思います。
#参考資料