玉石混合状態にあったFluxのフレームワークも、ここ最近ではReduxが首一つ抜け出したような感じとなっています。自分はFacebook/Flux派ではありましたが、先月発売された『WEB+DB PRESS vol.92』に掲載されていた伊藤直也さんのReduxの記事を読んで、Reduxを覚えてみようという気になりました。Redux自体はとてもシンプルで、とっつきやすいと思いました。ただReactとの連携はFacebook/Fluxと比べるとややこしい部分が多いかなといった印象です。ちょっとしたサンプルを作ってみたので、Reduxの実装方法とReactとの連携について紹介したいと思います。
目次
Reduxとは
Reduxは、Facebookの提唱するFluxアーキテクチャに基づいて(影響を受けて)設計されたJavaScriptフレームワークです。作者は@dan_abramov氏。
Fluxとは
Fluxというと、お馴染みとなっている以下の図の通り、アプリケーションをAction -> Dispatcher -> Store -> View (React)という4つの役割を持った部品で構築し、データは必ず一方向にしか流れないように設計するといったアプリケーションを構築するためのアーキテクチャです。
Reduxの特徴
Reduxはとてもシンプルです。大きくActions -> Reducers -> Storeの3つの部分から構成されます。上記のFluxの影響を受けていることから、データの流れは一方向になるようになっています。Storeが状態(state)を持ち、Actionが発生した際に、Reducerを使ってStoreの状態(State)を更新するといった仕組みとなります。
それから大事なことですが、状態(state)を格納するStoreはアプリケーションに必ず1つのみしか設けられません。1つのStoreですべての状態(state)を管理することになります。
Viewに関しては、Storeの用意するAPIを使うことで、stateの更新の購読と、stateの取得を行うことができるので、好きなようにしてくださいというスタンスです。React-Reduxというモジュールを使うと、Viewの実装にReactを使うことができるようになります。今回の記事では、このReact-Reduxを使ったReactとReduxの連携についても書いていく予定です。
Reduxの基本3原則
Reduxには以下のような基本3原則があります。これも一応頭の片隅に入れておくと良いでしょう。
Single source of truth
State is read-only
action(何が起こるか記述されているオブジェクト)を発火することが、stateを更新する唯一の方法です。
Changes are made with pure functions
stateがactionによってどのように変更されるか指定するために、純粋な(副作用のない)reducerを書きます。
Reduxの実装サンプル
サンプルはとても単純です。フォームに入力したテキストを表示させるだけのものとなっています。ReduxとReactを使って実装しています。
以下は、上記のサンプルを実装したサンプルコードです。簡単に説明するために、ファイルの分割などはせず、1つのファイルに全部書いています。参考にどうぞ。
Reduxの導入
早速Reduxのインストールから導入するまでを説明していきます。まず前提としてNode.js(npm)環境を用意しておく必要があります。説明は以下を参考にしてください。
それから、今回は説明を簡単にするために、ファイルの分割などはせず、1つのファイルにコードを全部書いていきます。
Reduxのドキュメントも合わせてご参照ください。
Reduxのインストール
npm経由でReduxをインストールします。まずプロジェクト用のディレクトリを作成し、そのディレクトリに移動してから、以下のコマンドを実行します。そうするとプロジェクトディレクトリ内にReduxがインストールされます。
Reduxのインポート
次に実際にコードを書いていくためのapp.jsファイルを作成し、そのファイル内にインストールしたReduxをインポートします。createStoreメソッドを読み込むようにします。
ES6とJSXのコンパイル環境(Babel)の準備
今回のコードはES6(ES2015)構文で書いていきます。最終的にブラウザで読み込めるES5に変換する必要があります。それから、Viewで使用するReactではJSX構文を使用するため、こちらも通常のJavaScriptに変換する必要があります。従って、こうしたコンパイル環境の構築も必要です。詳細は当ブログの以下の記事を参考にしてください。
追記: 以下は当記事を公開後に書いた記事です。こちらも参考にしてください。
- Reactを「webpack + babel-loader」でビルドする方法 – maesblog
- コマンド一発でReactの開発環境を構築してくれるFacebook製ツール「create-react-app」 – maesblog
それでは、以下よりReduxの実装について説明していきます。
Storeの実装
「Store」は状態を保持する役割を持ち、ActionとReducerをまとめるオブジェクトです。以下のような特徴があります。
- アプリケーションの状態(state)を保持します
- getState()メソッドを通して状態(state)へのアクセスを許可します
- dispatch(action)メソッドを通して状態(state)の更新を許可します
- subscribe(listener)メソッドを通してリスナーを登録します
- subscribe(listener)メソッドによって返された関数を通してリスナーの登録解除をハンドリングします
Storeの実装
Storeは、インポートしたReduxのcreateStore()メソッドを使って作成します。その際に、createStore()メソッドの引数として、「関数」として定義したReducer(後ほど説明します)と初期state(オブジェクト)を渡すようにします。今回はStoreに持たせるのは、シンプルにvalueという状態1つだけとします。
Storeの使用方法
今回は、React-Reduxを使用して、Viewの実装をReactで行うようにしますが、Reactを使用しない場合は、上記で説明したStoreオブジェクトのメソッドを使って、Viewの実装を行うことになります。上記のようにStoreを作成したら、以下のような使い方をします。
Actions/Action Creatorsの実装
「Action」は、アプリケーションからStoreにデータを送る情報のペイロードです。Storeにおける唯一の情報源となります。Actionは、先ほど作成したstoreのdispatch()メソッドの引数に渡し、Storeに送られます。
Actionの実装
Actionは「アクション名」(Reducerで処理を判別するため)と「状態の値」を持った単なるオブジェクトです。作成したActionは、「Action Creator」と呼ばれる関数の戻り値にセットします。今回は、SENDという名前(type属性)と、Viewから受け取ったvalueという値を持ったActionを作り、さらにsend()という関数(Action Creator)を作って、その戻り値としてActionを返すようにします。
Storeのdispatch()メソッドの引数にAction Creatorを渡すことで、ActionがReducerに送られます。
Reducersの実装
「Reducer」は、Actionに呼応してアプリケーションの状態(state)をどのように変化させるか指定する役割を持った関数です。受け取ったAcitonのタイプ属性を見て、対応するActionの値を用いて、Storeのsateを更新します。上記で説明した通り、Storeを作成する際に、ReducerをcreateStore()メソッドの第一引数として渡すことで、Store内でstateの更新が行えるようになっています。
Reducerの実装
Reducerは、「現在の状態(state)」と「受け取ったAction」を引数に取り、新しい状態を返す関数として実装します。switch文を使って、受け取ったActionの名前(type)を判別して処理を書くようにします。それから、Reduxの特徴的な部分となりますが、stateの更新を行う際にES2015のObject.assign()メソッドを使用しています。これは、stateそのものを変更させないようにするためです。Object.assign()メソッドを使用する際はpolyfillの使用が推奨されています。
今回の実装では、ActionのtypeがSENDの場合は、受け取ったActionのvalue値でStoreのstateを更新し、それ以外の場合は、元々のstateを返すようにしています。
ちなみに、Reducerは複数作ることができます。ただし、Storeを作成する際は、一つのReducerをcreateStore()メソッドにセットするようになっています。そこで、Reducerを複数作った場合は、Reduxで用意されているcombineReducers(reducers)メソッドを使って一つにまとめるようにします。
Reduxの実装の説明は以上となります。こうやってみるとReduxはかなりシンプルであることがわかると思います。Viewの実装も、Storeのところで説明したようにStoreで用意されているメソッドを使って行うことで実現可能です。他にもMiddlewareやStore enhancerなど覚える必要があるものもありますが、とりあえずこれだけ覚えておけばある程度のものは作れるようになるかと思います。
ただやはりReduxとReactは親和性が高く、Viewの実装はReactで行いたいところですね。ReactとReduxの連携について、以下より説明していきます。
Reactとの連携1: React-Reduxを導入する
ReduxとReactを連携させるには、React-Reduxというnpmのパッケージを使用します。
React-Reduxのインストール
React-ReduxはReduxには含まれていないので、新たにインストールする必要があります。以下のコマンドでワーキングディレクトリ内にReact-Reduxをインストールします。
React-Reduxのインポート
React-Reduxをインストールしたら、上のReduxの実装の説明で使用したapp.jsにインポートします。今回は、Providerクラスとconnectメソッドを読み込むようにします。これらの詳細は後ほど説明します。
Reactとの連携2: Reactを導入する
次にViewを実装するためのReactを導入します。
Reactのインストール
以下のコマンドでワーキングディレクトリ内にReactとReact-domをインストールします。
ReactとReact-domのインポート
React-Reduxと同様、上のReduxの実装の説明で使用したapp.jsにReactとReact-domをインポートします。
今回は、Reactに関する知識はある程度あるものとして話を進めていきます。当ブログではReactについての記事もいくつか書いているので、Reactについてはこちらをご参照ください。
Reactとの連携3: Viewを実装する
React-ReduxとReactを導入したら、早速連携したいところですが、まずはReactでViewを書いていきます。ここで大事なポイントがあります。ReduxのViewとしてReactを使う際は、Reactのコンポーネントを以下の2つのコンポーネントのどちらかとして実装するということです。
- Container components: 機能に関するコンポーネント。いわゆるFluxでいうContainerです。主に直接Reduxと連携するコンポーネントで、ReduxのStoreの状態(state)を購読し、またReduxのActionをDispatchする役割を持ち、データを取得したり、stateの更新を行ったりします。主に親コンポーネントがこの役割を担います。
- Presentational Components: 見た目に関するコンポーネント。Container componentsからpropsを通してデータを受け取り、Viewを構築します。また同様にpropsから受け取ったコールバックを実行します。
この考え方に関する詳細は、以下をご参照ください。結構大事な部分となります。
Container Componentsの実装
親コンポーネントをContainer Componentsとして実装します。子コンポーネントとして、FormInputコンポーネントとFormDisplayコンポーネントを紐付けるようにしています。それから、Propsを通して、ReduxのStoreと連携するようにしています。これは後ほど説明しますが、React-Reduxのconnect()()メソッドを使って実現しています。
Presentational Componentsの実装
Presentational Componentsは、見た目を実装するコンポーネントです。Container Componentsの子コンポーネントとして実装します。データやコールバック関数などをすべてPropsを通して親であるContainer Componentsから受け取るようにします。
こちらはテキストの入力フォームと送信ボタンを持ったコンポーネントになります。入力フォームはUncontrolled Componentsとなっています。ボタンを押した際に実行されるイベントハンドラ関数には、Container ComponentsからPropsを通して受け取ったコールバック関数がセットされていて、ReduxのActionがDispatchされるようになっています。
こちらは入力フォームに入力されたテキストを表示させるコンポーネントになります。こちらもContainer ComponentsからPropsを通して、valueの値を受け取るようになっています。
Reactとの連携4: ReactとReduxを連携させる
Viewを作成したら、最後にReact-Reduxを使ってReduxと連携させます。
Reactに連携させるReduxのStoreを渡す
ReactをReduxのViewとして機能させるには、まずどのReduxのStoreと連携させるかを決める必要があります。そこで、React-Reduxのインポートの際に読み込んだProviderクラスを使っていきます。
これはReactのコンポーネントとなっており、このProviderの子コンポーネントに、上で作成したReactのContainer Component(この後説明するReact-Reduxの
ProviderコンポーネントとReactのContainer Componentの連携は、React-domのrenderメソッドの引数内で行います。
連携したReduxのStoreをPropsを通してReactで使えるようにする
ReduxとReactを連携させたら、渡されたReduxのStoreをReact内で扱えるようにします。それにはReact-Reduxのインポートの際に読み込んだconnectメソッドを使用します。connectメソッドはカリー化されているので、見た目はちょっとややこしいですが、connectメソッドの引数にmapStateToProps関数とmapDispatchToProps関数を渡し、connectメソッドの戻り値にセットされている関数の引数にContainer Componentを渡すようにします。
mapStateToProps関数とmapDispatchToProps関数は、それぞれStoreのstateとdispatchメソッドをpropsを通して、Container Componentで扱えるようにするものです。詳細は以下をご参照ください。
connect()()メソッドの一つ目の引数に、Storeのstate.valueを「value」として、dispatch(send())メソッドを「onClick()」として渡し、Container Component内でpropsを通して扱えるようにします。2つ目の引数にはラップする対象となるContainer ComponentのFormAppコンポーネントを渡して、AppContainerという変数に格納します。
AppContainerをレンダリングの際にReact-ReduxのProviderコンポーネントの子コンポーネントにセットすることで、ReactとReduxの連携は完了です。
ReduxとReactで実装したサンプルのソースコード
最後に、これまで説明してきたコードをひとつにまとめておきます。
今回のサンプルのソースコードはGitHubにもアップしていますので、git cloneでもしてお試ししていただければと思います。使用方法などはGitHubのREADMEに書いてあります。
まとめ
結構説明は長くなりましたが、Reduxのみであれば、Redux自体がかなりシンプルな作りとなっているので、理解もしやすいかと思います。むしろややこしいのがReact-Reduxを使ったReactとReduxの連携の方だと思います。特にconnectメソッドの部分で、自分も最初何のことやらと戸惑いました。実際に動きを確認しながら実装してみることで、それなりに理解できるようになるかと思います。
それから、やはりReduxはFacebookの提唱するFluxアーキテクチャを元に開発されているので、Flux自体もちゃんと理解しておくと、Reduxのそれぞれの機能やReactのContainer化の部分なども理解しやすくなると思います。当ブログではFluxについての記事もいくつか書いているので、Fluxについてはこちらをご参照ください。
いろいろ乱立していたFluxフレームワークもRedux一択といった流れになってきています。そろそろ本腰入れてReduxも覚えておいてもよい時期だと思います。
最後に伊藤直也さんによるReduxの記事が掲載されているWeb+DB PRESSを紹介しておきます。これはすごく貴重な情報源になるかと思います。
コメント
ピンバック: React Redux Sample1 (Under constuction) | Professional Programmer
ピンバック: React Redux Sample1 (Under construction) | Professional Programmer