先日『Flux Utilsのドキュメント日本語訳』というFlux Utilsのドキュメント英語版を翻訳した記事を投稿しました。Flux Utilsは、Facebook製のFluxフレームワークとでもいうでしょうか。ただ残念なことに、このドキュメントを読むだけでは、Flux Utilsの使い方のイメージを完全に掴むことは難しいと思います(自分がそうでした…)。ということで、Flux Utilsを使って、簡単なサンプルを作ってみました。この簡単なサンプルを通して、Dispatcher.jsだけを使ったFluxの実装方法と比較しながら、Flux Utilsの使い方を説明していきます。
目次
はじめに(Flux Utilsとは)
Flux Utilsは、Facebook製のFluxフレームワークとなります。ただ、すべての用途に対応するような完成されたフレームワークではないとFlux Utilsのドキュメントには書いてあります。Flux Utilsで対応できないケースが発生したら、Reduxなど他のFluxフレームワークを使ってくださいというスタンスのものです。
詳細は、Flux Utilsのドキュメントをご確認ください。
Flux Utilsの特徴
Flux Utilsの大きな特徴は、「Store」と「Container」になるでしょう。Fluxは、いわゆる「View」「Action」「Dispatcher」「Store」の4つの部位で構成されるアーキテクチャと説明されますが、Flux Utilsはこの「Store」と「View」の部分を強化するユーティリティとなります。
Flux Utilsで用意されているユティリティクラス
<Store向けユーティリティクラス>
- Storeクラス: dispatcherを受け取り、storeのインスタンスの作成と登録を行うベースクラス
- ReduceStoreクラス: stateを作成/保持し、actionをreduceしてstateを更新するクラス(Storeクラスを継承している)
- MapStoreクラス: stateをimmutableなmapとして定義するクラス(ReduceStoreを継承している)
<View向けユーティリティクラス>
- Container: View(React)のルートComponentをセットし、Viewをコントロールするクラス。関連のあるStoreに変更があった際にそのstateを更新し、Viewに変更を通知する(ReactのsetState()メソッドのような振る舞い)。
Dispatcher.jsだけで実装する場合との違い
Facebookが用意してくれいてるDispatcher.jsのみでも、Fluxの実装は可能です。当ブログでも、Dispatcher.jsを使ったFluxの実装方法について以下の記事を投稿しています。
Dispatcher.jsのみでFluxを実装する場合と、Flux Utilsを使ってFluxを実装する場合での大きな違いは、簡単に言うとStoreからViewへデータを渡す仕組みを自分で作る必要がないということになります。Dispatcher.jsのみでFluxを実装する場合は、Storeのstateに変更があった際は、Store側でカスタムイベントを発火 => View側でイベントを監視 => setState()を実行しViewを再描画といった一連の処理を書く必要があります。
この辺の違いがわかるように、この後のサンプルの説明では、Dispatcher.jsのみでFluxを実装する場合と、Flux Utilsを使ってFluxを実装する場合を比較しながら行っていきます。
サンプル
サンプルはとても単純です。フォームに入力したテキストを表示させるだけのものとなっています。Flux Utilsを使って実装しています。
以下は、上記のサンプルをFlux Utils、Dispatcher.jsのそれぞれで実装したサンプルコードです。簡単に説明するために、ファイルの分割などはせず、1つのファイルに全部書いています。参考にどうぞ。
環境の準備
まずは、環境の構築から説明していきます。基本的にnpmを使った方法となります。ここでは説明を省きますが、事前にNode.js環境を用意しておく必要があります。
インストール
以下のコマンドを使って、React(View用)とFluxをnpm経由でインストールします。
Reactのインストール
Fluxのインストール
インポート
次に実際にコードを書いていくためのapp.jsファイルを作成し、インストールしたReactとFluxをファイルにインポートします。Fluxに関しては、fluxモジュールからDispatcherクラス、flux/utilsモジュールからReduceStoreクラスとContainerクラスを今回はインポートするようにします。
JSXとES6のコンパイル環境の準備
今回のコードはES6(ES2015)構文で書いています。最終的にブラウザで読み込めるES5に変換する必要があります。同時にReactで使うJSXの変換も必要です。そうしたコンパイル環境の構築も必要です。詳細は当ブログの以下の記事を参考にしてください。
それでは、早速Flux UtilsでのFluxの実装方法について説明していきます。
Dispatcherの実装
「Dispatcher」は、Viewで実行されたActionを受け取り、Actionにおいて指定された適切な配送先(Store)にデータを配送する場所となります。
Dispatcher.jsのみでの実装の場合も、Flux Utilsでの実装の場合も、Dispatcherの実装は以下のみです。Dispatcherモジュール(dispatcher.js)を読み込むだけでOKです。(Flux Utilsを使う場合は、Storeのインスタンスを生成する際に、コンストラクタの引数として渡します。)
Dispatcher.jsについての詳細は、当ブログの以下の記事をご参照ください。
Actionの実装
「Action」は、Viewで実行されたActionに応じて、適切なデータをSoterに伝える場所です。Viewで実行されるAction(setterではなく、あくまでもユーザーのアクション)を定義します。
ViewでActionが実行されたら、対応するDispatcherのdispatch()メソッドを実行させます。Storeに送るデーター(ペイロード)は、dispatch()メソッドの引数に、オブジェクトリテラル形式でセットします。その際にStoreがActionを識別できるように、Actionごとに名前(アクションタイプ属性)をつけておきます。
Dispatcher.jsのみでの実装の場合も、Flux Utilsでの実装の場合も、実装方法は同じです。
サーバーとの非同期通信は全てこのActionの部分で行うようにします。
Storeの実装
「Store」は、Dispatcherによって送られたデータを記録し、更新する場所です。Dispatcherからの特定のactionに応答し、データに変更があったら変更をemit(発火)させます。View(Container)がデータを取得できるようにpublicなgetterを持ちます。
(比較)Storeを「Dispatcher.jsのみ」で実装する場合
Dispatcher.jsのみでStoreを実装する場合は、すごく複雑でした。StoreからViewへデータを渡す仕組みを自分で作る必要がありました。
Dispatcher.jsのみでのStoreの実装方法の詳細については以下をご参照ください。
Storeを「Flux Utils」で実装する場合
Flux Utilsを使うとシンプルにStoreを実装することができます。今回はFlux UtilsのReduceStoreを使ってサブクラスを定義します。getInitialState()メソッドを使ってStoreの初期stateを作成し(ReactのgetInisialState()みたいなものです)、reduce()メソッドでDispatcherによって送られたデータを受け取り、受け取ったアクションタイプ属性を見て、対応するActionを実行します。
定義したReduceStoreのサブクラスをnewします。その際に、対応するDispatcherのインスタンスを引数にセットします。
stateに変更があった場合は、ReduceStoreが自動的にStateの変更をemitしてくれるようになっています。またReduceStoreは、getState()メソッドというstateを公開するためのgetterを持っています(Viewの更新時に使用します)。
Viewの実装
Viewは、アプリケーションのUIを担う場所です。主にReact.jsを使って実装します。UIとレンダリングのロジックのすべてを持ち、propsを通してすべての情報とコールバックを受け取るようにします。
まずはReactのRootコンポーネントを「Dispatcher.jsのみ」で実装する場合と、「Flux Utils」で実装する場合を見てみることにします。
(比較)Viewを「Dispatcher.jsのみ」で実装する場合
Dispatcher.jsのみでViewを実装する場合は、若干複雑でした。Storeのイベントを監視して、リスナーを実行する仕組みを自分で作る必要がありました。
Dispatcher.jsのみでのViewの実装方法の詳細については以下をご参照ください。
Viewを「Flux Utils」で実装する場合
Flux Utilsを使うとだいぶわかりやすくViewを実装することができます。ポイントはContainerによってコントロールされるようにすることです。そのためにRootコンポーネントには、Static(静的)なgetStores()メソッドとcalculateState()メソッドを持たせるようにします。
getStores()メソッドで、監視するStoreを指定して、calculateState()メソッドでStoreのstateを取得して、stateに変更があった場合Viewの更新(setState)を行います。
Viewの子コンポーネントの実装
Viewの子コンポーネントは、「Dispatcher.jsのみ」で実装する場合も、「Flux Utils」で実装する場合も、普通にReactで実装します。
Containerの実装
「Container」は、ViewをコントロールするReactコンポーネントです。主な役割は関連のあるstoreに変更があった時に、そのstateをupdateすることです。propsやUIロジックは持たないようにします。
ReactのRootコンポーネントをContainerに変換するために、create()メソッドの引数にセットします。
レンダリング
react-domのrenderでレンダリングします。その際には、Containerに変換したReactコンポーネトを指定するようにします。
Flux Utilsで実装したサンプルのソースコード
最後に、これまで説明してきたコードをひとつにまとめておきます。
簡単に処理の流れを書いておきます。
- Storeの初期stateを作成
- Viewのテキスト入力フォームにテキストが入力される
- Viewのフォームの送信ボタンが押される
- Viewの_send()メソッドが実行される
- フォームに入力されたテキストを引数にセットしてFormAction(Action)のsend()メソッドを実行
- Actionのsend()メソッドにより、Viewから送られたデータとアクションタイプ属性を引数にセットし、Dispatcherのdispatch()メソッドを実行
- Dispatcherによって送られたデータをStoreで受け取り、対応するActionを実行
- Storeのstateを更新
- Viewで関連のあるStore(formStore)を監視
- formStore(Store)のstateを取得して、stateに変更があった場合Viewの更新
- 更新されたデータをViewの子コンポーネントに渡す
- propsで渡された値を取得しViewに表示
まとめ
とてもシンプルなサンプルですが、Fluxで実装するとかなりボリュームのあるコード量となります。書くのは大変かと思いますが、一度書いてしまえば、すごくデータの流れなども把握しやすく見通しの良いコードになるかと思います。
特に今回取り上げたFlux Utilsを使うと、StoreとView(Container)の連携の部分がとてもシンプルに書けるようになります。この部分はFlux Utilsを使うメリットになるのではないでしょうか。それから今回は使いませんでしたが、MapStoreを使うと、stateをimmutableなmapとして定義することができるようになるので、大規模な開発を行う際には役に立つのではないでしょうか。
Fluxのフレームワークと言ったら、Redux一択といった流れになってきていますが、Flux Utilsも用途によっては十分に使えるフレームワークだと思います。とは言うものの自分はまだReduxに関してはほとんどわかっていない状態です。ある程度Fluxについては理解してきたので、次はいよいよReduxにでも手を出してみようかと思っているところです。
今回のサンプルのソースコードはGitHubにもアップしていますので、git cloneでもしてお試ししていただければと思います。使用方法などはGitHubのREADMEに書いてあります。
最後に、参考にさせていただいたサイトと書籍を紹介しておきます。
参考サイト
参考書籍
伊藤直也さんによるFluxフレームワークの一つであるfluxxorを使ったFluxの詳しい解説記事(全9頁)があります。Flux Utilsとの実装方法の違いなど比べてみるもの面白いかと思います。
コメント