ども、Simi (@simi_blog) です。
Reactは状態が変化するたびに表示を変化させる仕組みを用いていますが、その状態管理をお手軽に行えるReduxの基本についてまとめます。
お手軽といっても自分的には結構難しく感じていますが、React特有の親から子へ継承しまくる書き方をしなくて済むのは楽だと感じます。
どこの会社か分からないけど、以下の社内スライドが分かりやすいので本記事と合わせて読んだ方が理解の助けになるかもです。Reactはある程度理解できたけどReduxがよく分からないって人向けです。
Reduxとは
ReduxはReactが扱うUIの状態 (state) を管理するためのフレームワークです。公式サイトはこちらです。英語ですが公式のマニュアルもあります。
基本的な流れはactionの実行が確認されるとreducerが呼ばれてstore内の状態 (state) が更新される感じです。状態をstoreという場所で管理して、reducerという仕組みで状態を更新してくれます。
React+Reduxの始め方
Reactの環境構築が終了している前提で話していくので、環境構築方法が気になる人は以下記事を参考してください。
必要パッケージのインストール
以下コマンドで必要なパッケージをインストールします。yarnを導入していない人はnpmでOK。「redux」はstoreを使用するために、「react-redux」はReactとReduxを繋げるためのconnectメソッドとProviderコンポーネントを使用するために絶対必要なパッケージです。
$ yarn add redux react-redux
ファイル構成(僕の場合)
僕が勉強した方法ですと、以下のようなファイル構成でプロジェクトを管理していました。参考までに。
|-build |-node_modules |-public |-src |-actions | |-index.js |-components | |-App.js |-reducers | |-count.js | |-index.js |-index.css |-index.js
Actionの役割/使い方
Actionの役割
Actionは「何をする」という情報を持ったオブジェクトです。情報を羅列しているだけなので、処理自体は次に説明するreducerにお任せしています。
たぶんだけどreducerで使うデータもここで用意して、ディスパッチが起こったらreducerにデータを渡すんだと思います。
Actionの使い方
Actionは必ずtypeプロパティを持ちます。例えば加算と減算に関する動作は以下のように記述します。
{ type: 'INCREMENT' } { type: 'DECREMENT' }
Actionを定義しただけでは使用できませんので、作成したactionを返す関数を作成します。Reduxではactionを返す関数のことをActionCreaterと言います。上記actionを返すActionCreaterは以下のようになります。
const increment = () => ({ type: 'INCREMENT' }) const decrement = () => ({ type: 'DECREMENT' })
上記コードは少しリファクタリングされていて、丁寧に書くならactionを返すためのreturn()の中にtypeを書いても良いですね。
実際に使用する場合は自分で作成したactionsディレクトリの中にindex.jsを作成し、そのファイルに以下を記述します。
export const INCREMENT = 'INCREMENT' export const DECREMENT = 'DECREMENT' export const increment = () => ({ type: INCREMENT }) export const decrement = () => ({ type: DECREMENT })
変数は1か所で定義した方が良いので、上部で定義しておきます。またビューを担当するcomponentでactionを使用するので、ActionCreaterをexportして他モジュールでimportできるようにしておきます。
Reducerの役割/使い方
Reducerの役割
Reducerはactionに含まれるtypeに応じてstate(状態)をどう変化させるかを定義するオブジェクトです。actionと現在のstateを元に新しいstateを作成して返す関数を定義します。
Reducerの使い方
実際に使用するときはredusersディレクトリを作成して、その中でindex.jsとreducerのjsファイルを作成していきます。今回はindex.jsと数字をカウントするcoujnt.jsを作成してみます。
index.jsではすべてのreducerを1つのreducerにまとめる内容を記述します。
import { combineReducers } from 'redux' import count from './count' export default combineReducers({ count })
Storeを作成するときには、このまとめられたreducerをimportするのでexportしています。大規模なプロジェクトではreducerが多くのなるので、combineReducers({})の中身はカンマ区切りで記述していきます。
次にreducerであるcount.jsを作ります。
import { INCREMENT, DECREMENT } from '../actions' const initialState = { value: 0 } export default ( state = initialState, action) => { switch (action.type) { case INCREMENT: return { value: state.value + 1} case DECREMENT: return { value: state.value - 1} default: return state } }
reducerは先ほど述べたように関数で定義します。引数は2つ。1つ目が初期状態で2つ目がactionです。
stateは初期では空なので、初期値をstate=initialStateで指定しています。
actionのtypeに応じて処理を書いていくので、switch文で処理を分岐させます。
このように実際の処理はreducerで行っていることが分かります。
Storeの役割/使い方
Storeの役割
Storeは状態 (state) を保持している場所であり、アプリケーション内でただ1つだけ存在します。
Storeの使い方
まず準備としてメインのビューであるrootのindex.jsで以下を追記します。
import { createStore } from 'redux' import { Provider } from 'react-redux' import reducer from './reducers'
Storeを作成するための関数であるcreateStoreと作成したstoreを全コンポーネントに提供する機能を持つ特殊なコンポーネントであるProviderをimportします。また先ほど作成したreducerもimportしています。
次に引き続きindex.js内でstoreを作成します。以下コードで作成完了。
const store = createStore(reducer)
実際に表示する部分は以下のように書きます。
ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
ここで書いてるAppはrootもしくは自分で作成したビュー用ディレクトリ内のApp.jsのAppコンポーネントを指していると想定しています。
このAppコンポーネントをProviderコンポーネントで包んであげることで、ビュー用コンポーネント全体でstoreを共有できるようになるんですね。これは便利。
connectでReactとReduxを連携させる
Action/Reducer/Storeを作成しただけでは、ReactとReduxは連携してくれないです。これはstateを管理しているのはReactのコンポーネントではなくReduxのstoreだからです。お互いの世界は分断されているのです。ここ大事。
そこでReactとReduxの世界を繋げるために必要なのがconnectメソッドです。connectメソッドは引数が4種類あって使い方も12種類あるらしいんですが、今回は一番オーソドックスなやつを紹介します。
使い方としてはReactのビュー用のjsファイル(先ほどの例だとApp.js)に以下を記述します。
export default connect(第1引数, 第2引数)(子コンポーネント)
第1引数にはmapStateToProps、第2引数にはmapDispatchToPropsを指定します。nullにする使い方もありますが、今回は詳しくは解説しないです(僕自身があまり理解していないので)。
子コンポーネントは先ほどの例だとビュー用のAppコンポーネントが該当します。ここでいう親コンポーネントはrootのindex.jsにおけるReactDOM.renderだと思ってもらって問題ないかと。
先ほどの例だとこんな感じ。
export default connect(mapStateProps, mapDispatchToProps)(App)
mapStateToPropsは子コンポーネントにstoreの変更を知らせるためのメソッドで、mapDispatchToPropsは子コンポーネントにdispatchを知らせるためのメソッドで、それぞれ以下のように定義しておきます。
const mapStateToProps = state => ({ value: state.count.value })
const mapDispatchToProps = dispatch => ({ increment: () => dispatch(increment()) decrement: () => dispatch(decrement()) })
mapStateToPropsでは子コンポーネント内でstateのどの値を読み取りたいかを選択します。上記の例だとstore内のstate.count.valueをvalueという名前でAppコンポーネント内で使用できるようにしています。Appコンポーネント内ではthis.props.valueという形で使用可能です。
mapDispatchToPropsではActionCreaterを渡すことで子コンポーネントで使用するactionをディスパッチさせることができます。上記の例だとAppコンポーネント内でincrement/decrementのactionをディスパッチさせることができます。ここでactionがディスパッチされるとreducerが反応して適切な処理が実行されるわけですね。Appコンポーネント内ではthis.props.incrementみたいな感じで使用します。
まとめ
今回はReact+Reduxの基本であるAction/Reducer/Storeについて軽く解説しました。
基本的な流れはactionがディスパッチされてreducerが呼ばれてstore内の状態 (state) が更新される感じです。
Reduxは表記方法が色々あるせいで頭が混乱してきます。実は何度か挫折してます。今も挫折しそうだけど頑張って勉強を続けていきます。ちなみに僕はUdemyとQitaで勉強しています。それでも正直挫折しそうです。頑張りましょう。
コメント