moxt

Just another Blog site

ReactとFluxとReduxについて順を追って整理する

      2016/02/02

書き途中

React

Viewのライブラリ。
なぜ、従来のjQueryやBackbone.jsやVue.jsではダメなのか。

今話題のReact.jsはどのようなWebアプリケーションに適しているか? Introduction To React─ Frontrend Conference

まず、jQuery。
コイツでWEBアプリを作るとコンポーネントの境界を超えた破壊的操作が容易にできる。
強力だけど、誤った破壊的操作を仕込めば複雑な依存関係を生む。
結果として例えば『あるメソッドを叩くと意図しない挙動がおきてしまう。。』なんてことがある。
もちろん、親のコンポーネントを直接操作しないとか、モデルとビューを分けておきモデルの変化をビューが受け取りレンダリングをする、といったことをjQueryではできる。ただ、これは『頑張って自分で作るなりすればできる』という話。
jQueryはViewのコンポーネント化やMVCっぽい処理はサポートしていない。

Backbone.jsやVue.js
これらはViewのコンポーネント化やMVCっぽい仕組みを用意してくれてるフレームワークだ。
なので、jQueryよりは制約が多いものメンテナンスがしやすいWEBアプリが作れる。
これで万事OKだ!
と、いうわけではないようだ。
先に挙げた記事ではBackbone.jsやVue.jsは大規模なアプリには向いてないらしい。
この主張に共感できるほど大規模なアプリを書いたことがないので無理にReact使わないでBackbone.jsとかでもいいじゃん。と思ってる。

ReactはViewのライブラリ。
Componentという描画用クラスを組み合わせてアプリを構築してゆく。

ルートComponent –> [ ヘッダーComponent, タイムラインComponent –> [タブComponent, フィードリストComponent], フッターComponent ]

こんな感じでツリー構造を作ってく。
基本的にはルートComponent以外はステートレスであることが望ましいらしい。
状態は常に親コンポーネントから与えられ、子コンポーネントは与えられた状態をただ描画するだけ。

TODOアプリで新規作成ボタンをクリックしたらTODOアイテムが追加される、ってときはどうすればいいのだろう。
状態はあくまでルートComponentのみが保持してる。なので、子コンポーネントはどうにかしてルートComponentに『TODOアイテムが1つ追加されたわ。それから新しい状態が欲しいわ』という要望を伝えつつ、新たな状態をいただく必要があるのだ。
この要望はルートComponentから引き渡されてきたコールバック関数を叩くことで実現できる。

では、階層が100くらいあるふかーいComponentツリーの場合どうだろうか(こういうツリーを設計することそのものがダメな気がするが。。)。
そう、ルートComponentからコールバックを末端のComponentまでズルズルと引きずり回さないといけないわけ。
これは大変だし、直接関係ないComponentにもコールバックを与えるのは気持ち悪い。

Contextという仕組みを使えばツリー階層を飛び越えてルートComponentの状態を子コンポーネントに参照させることもできるが、使い方を誤ればjQuery時代に逆戻り。
EventBusを介してメッセージを飛ばす方法もいくつか提案されてる。

FacebookはFluxという1つの解を提案する。

Flux

例の図。

flux-diagram

View –> Action –> Dispatcher –> Store –> View …
Fluxの特徴は情報の伝播を1方向に制限している点。

View

ViewはReactがやる。

Action

Actionは『何を、どうした』という情報を表現するオブジェクト。
具体的には、TODOアイテム(idはXXXで、textは◯◯)を新しく1件追加した、といったまさにアクションを表現してる。
Actionは同期的に処理できるようにしておく。
ネットワーク通信が必要な処理(追加したTODOアイテムをサーバ側で永続化するなど…)はAction側、上図に描かれてるActionCreatorと呼ばれるオブジェクトが取りまとめる。
ActionCreatorがWebAPIを叩くオブジェクトに処理を任せ、WebAPIからのレスポンスを適当なActionオブジェクトに加工して後述するDispatcherに送りつける。
WebAPIのエラーハンドリングもActionCreatorでやるし、エラーに応じて適当なActionを生成してDispatcherに送りつける。

Dispatcher

DispatcherはPubSubを担うオブジェクト。
Facebookによる実装例はとてもシンプル。
registerメソッドでコールバックを登録しておき(Sub:購読)、dispatchで登録された全てのコールバックをPayload(Actionオブジェクト)を渡しつつ実行する(Push:出版)。

それぞれのメソッドはどのオブジェクトによって叩かれてるか整理する。
先ほど出てきたActionCreatorがDispatcherのdispatchメソッドを叩いてる。
registerは次に紹介するStoreが叩く。
あらかじめStoreはregisterでDispatcherからの通知に耳を傾ける準備しておく。
で、ActionCreatorからDispatcherに対してActionオブジェクトをdispatch(速達)されると、Dispatcherはcallbackを実行する。これがStoreへの通知を実現してるわけ。
オブジェクト間の情報の流れが1方向になってるように見える。

Store

状態を管理するオブジェクト。
状態とは、TODOアプリを例にすればTODOのリストであったり、個々のTODOの具体的な内容、達成状況や期限といった情報を指す。
fluxにおいて状態の変更はActionによってのみ実現される。
Viewから直接Storeの中身をこねくり回す、とかはNG。
そして、状態の変更はViewへと伝播する。
これでViewからStoreまで1周したことになる。

StoreではDispatcherに対してコールバックのregister(登録)を行ってる。
Dispatcherに対してActionをdispatchすると、Dispatcherに登録されてるすべてのコールバックが実行される。
関係ないActionだろうがなんだろうがとにかく実行される。
なので、コールバック側では届いてくるActionをフィルタリングして、適切なActionに応じた状態管理・通知を行う。

実際コールバックを登録してる例は下記の通り。

これくらいのAction量なら素直にswitch文で分岐させてればいいけど、規模がデカくなってきたときに困りそうだね。。
ここは悩ましいすね。

Fluxの実例

TODOアプリでTODOを追加する過程をViewからStoreまで追ってみる
facebook/flux

View -> Action

追加するTODOの内容を書き込んで、アイテム追加を依頼するコンポーネントはTodoTextInputってヤツ。

https://github.com/facebook/flux/blob/master/examples/flux-todomvc/js/components/Header.react.js

onSaveプロパティにthis._onSaveっていうコールバック関数を渡してる。
TodoActions.create(text)してますね。

Action -> Dispatcher

https://github.com/facebook/flux/blob/master/examples/flux-todomvc/js/actions/TodoActions.js
TodoActionsのcreateではAppDispatcherにActionオブジェクト(単なるKey-Value)をdispatchしてる。

Dispatcher -> Store

AppDispatcherはFacebook流Dispatcherを呼び出してるだけ。
全てのcallback関数を実行する。

Store -> View

https://github.com/facebook/flux/blob/master/examples/flux-todomvc/js/stores/TodoStore.js#L124

ここでstore内のcreateが実行される。
storeは内部に_todosという配列でTODOアイテムを一元管理してる。

無事にTODOアイテムが追加されたら、TodoStore.emitChange()が実行される。
あらかじめStoreの状態変化を検知できるようにView側からcallbackを登録しており、emitChangeをトリガーにしてcallbackが実行されるわけ。

ということで、Viewを見てみる。
FluxではrootなコンポーネントがStoreとの連携を行う。
なので、データの更新は常にrootから天下ってくる。
rootより下層でStoreと連携するような設計にしても良いけど、仕様変更とかで同じ層の兄弟コンポーネントにも情報を渡さないといけない、、ってなったときに破綻する。
なので、よほど揺るがない仕様でない限りはrootでStoreとの疎通を行うのがよい。

そんなわけでTODOアプリはTodoAppコンポーネントでStoreとの連携を行ってる。
コンポーネントがマウントされたタイミングで_onChangeをコールバックとして登録してる。
_onChangeではthis.setState(getTodoState())が呼ばれてる。
Storeで管理されてるTodo状態をsetState(Reactのメソッド)を実行することでView側と同期してる。

TodoAppコンポーネントとStoreが互いに参照してる状態なので、アンマウントされたタイミングでcallbackの解除をやってる。

Fluxなんとなく分かってきた。
アプリの仕様が複雑になるとネットワーク通信が必要になったり、あるActionで複数のStoreが反応してしまいStore間で競合が発生したりと、ちょっとした問題がいくつか出てくる。

  • Fluxで非同期処理をどのように捌くか
  • waitForでStore間の依存関係を管理する

この辺は知っておきたい。

Redux

ReduxはFluxの実装の1つ。
Action,Reducer,Storeからなる。

Action

Actions are payloads of information that send data from your application to your store.

FluxのActionと同じ。
『TODOアイテムを新規に追加した』といった情報を表現するオブジェクト。
FluxではActionをDispatcherに与えることでStoreの状態を更新したりしていた。
ReduxではActionをStore(正確にはStoreの中のReducer)に与えることでStoreの状態を更新する。

Reducer

Actions describe the fact that something happened, but don’t specify how the application’s state changes in response. This is the job of a reducer.

アクションを受けてどのようにStoreの状態を更新するかはReducerの責務。

具体例をあげてみる…

『TODOアイテムを新規に追加した』というアクションをStoreに対してdispatchする。
すると、Reducerたちが受け取ってイイカンジに処理する。

あるReducerはTODOアイテムの情報をStoreに挿入する。
別のあるReducerはStoreにある未完了TODOアイテム数の情報を更新する。

このようにReducer毎に『Actionを受けて何をするか』が異なる。

react-redux

Reduxはアプリの状態管理を担当するものなので、View(React)との連携が必要になる。
先述のFluxの例ではStoreとルートコンポーネント間をlistenerを通じて連携していた。
Reduxの場合でも同じようにStoreとルートコンポーネント間で連携させる。
その辺をイイカンジにやってくれるのがreact-reduxだ。

書く

 - javascript, プログラミング

  • このエントリーをはてなブックマークに追加
  • follow us in feedly

  関連記事

no image
YosemiteでRubyMineが起動できない

yosemiteからjavaが1.7系になってる。 一方、rubymineは1.6系を想定している。 …

no image
RDSの特定のデータベースをダンプする

Publicly Accessibleがyesならどこからでも下記が実行可能。 …

no image
Python(Anaconda)とOpenCVを使って動画から顔画像を抽出してみる

今話題のディープラーニングをやってみたい。 いろいろ見た感じCaffeというフレームワークが良さそう。 …

large_v
Docker Machineのメモ

随時追記する Contents1 …

no image
RubyMine(OSX)でGit操作するときのコマンド集

GITを操作系のポップアップを表示したい

large_v
MacでDockerした感想文

Contents1 前提:DockerはLinux上でしか動かない1.1 …

no image
iOSアプリの設計ってどうやるの?

ViewControllerに処理を詰め込みすぎて保守が大変になるのはあるあるネタですよね。 じゃあ、ViewControllerでやることってなんだろうって問われると、まあ、こんなんだろうなってフワッとしてる。 …

no image
Toolbarで表示する矢印アイコンの色を変えたい

http://stackoverflow.com/questions/26788464/how-to-change-color-of-the-back-arrow-in-the-new-material-theme 動的に色を変えるイイカンジな方法が分からず困っていた。 …

no image
単語の出現頻度をlinuxコマンドだけで調べたい

無駄にpythonとか使おうとしてた。。 楽にできて良かった。 …

no image
gitであまり使わないけど知らないと困るコマンド一覧

随時追加 originのURLを変更したい …