この記事はVirtual DOMアドベントカレンダーの5日目の記事です。
Virtual DOMといえばReact。
ですが、ReactはMVCで言うViewの部分だけを担当するのであって、MVCフレームワーク的なものではありません。
なので、Model等、他の部分は自前でどうにかする必要があります。
そこで、facebookの中の人は、Reactを中心にしたアプリケーション構築パターンとしてFluxってのを提唱してます。
https://facebook.github.io/flux/
Fluxってどんな
Flux、ざっくり言うと、
- データは1方向に流れる
- イベントをActionとして定義
- ロジック、データはStoreに全部入れる
というアーキテクチャ。
図で言うと、MVCでは
↑みたいになるとM,C間の処理の流れムズかったのが、
Fluxだと
という風にデータの流れが整理できて嬉しいですよ、ということ。
(図はfluxxorの解説から拝借)
Fluxの詳しい解説はできないので省略
↑のページ読みながらsaneyukiさんの記事よんだら大体わかりそう
Fluxのめんどい点
さて、Fluxに従ってアプリ書くと、
- DispatcherやActionCreatorで同じコード何度も書く事になる
- 「何でもActionで処理する」ってしないと、Componentから他のComponent触ったり、生のDOMを触る時に面倒がってComponent内にロジック書いちゃったりして破綻しがち
- Storeの書き方とかどうにでも出来てしまい、統一感なくなって困る
それもこれもFluxがフレームワークじゃないのが悪い!
というわけでFluxxorが生まれた。
fluxxorでどう便利になるか
ここでは、Fluxxorを使ったコードが素のFluxで書いた時とどういう感じに違うのか見ていく。
Fluxxor、公式ページにTODOリストのチュートリアルある。
facebook/Fluxの方にもTODOリストのチュートリアルあるけど、Fluxxorの方が簡単な作りになってる。
以下では、Fluxxorの方のチュートリアルにそって解説する。
Store
素のFluxではStoreの作り方とか決められていないけど、一応チュートリアルだと↓みたいに書いてる。
var AppDispatcher = require('../dispatcher/AppDispatcher'); var EventEmitter = require('events').EventEmitter; var merge = require('react/lib/merge'); // データ置き場 var _todos = []; var addTodo = function (text) { _todos.push({text: text, complete: false}); }; var TodoStore = merge(EventEmitter.prototype, { getState: function () { return { todos: _todos }; }, addChangeListener: function(callback) { this.on('change', callback); }, removeChangeListener: function(callback) { this.removeListener('change', callback); } dispatcherIndex: AppDispatcher.register(function(payload) { var action = payload.action; var text; switch(action.actionType) { case TodoConstants.ADD_TODO addTodo(action.text); this.emit('change'); break; // ...省略... } return true; }) };
merge(EventEmitter.prototype, {...})
とかAppDispatcher.register()
とかやってて覚えられない。
これがFluxxorだとこうなる。
var TodoStore = Fluxxor.createStore({ initialize: function() { this.todos = []; this.bindActions( constants.ADD_TODO, this.onAddTodo, ); }, onAddTodo: function(payload) { this.todos.push({text: payload.text, complete: false}); this.emit("change"); }, getState: function() { return { todos: this.todos }; } });
Storeがクラスになったので大分心が楽になる。
また、this.emit('change')
してる所は、後述するStoreWatchMixinによりComponent側で監視できる。
ActoinCreator
素のFluxだとこうで
var AppDispatcher = require('../dispatcher/AppDispatcher'); var TodoActions = { create: function(text) { AppDispatcher.handleViewAction({ actionType: TodoConstants.TODO_CREATE, text: text }); }, };
Fluxxorだとこうなる
var TodoActions = { addTodo: function(text) { this.dispatch(constants.ADD_TODO, {text: text}); }, };
this.dispatch()
とかやってて、これ普通にrequireしてTodoActions.addTodo()
とかしても使えない。
Fluxxorでは、↓のようにしてStoreとActionを結びつけ、最上位のComponentのpropsに渡す必要がある。
var stores = { TodoStore = new TodoStore() }; var actions = TodoActions; var flux = new Fluxxor.Flux(stores, actions); React.render(<Application flux={flux} />, document.getElementById("app"));
Component (View)
ComponentはReactで普通に書く。基本素のFluxの時と変わらないんだけど、
Store/Actionを触るためにFluxMixin
をmixinする。
var FluxMixin = Fluxxor.FluxMixin(React), StoreWatchMixin = Fluxxor.StoreWatchMixin; var Application = React.createClass({ mixins: [FluxMixin, StoreWatchMixin("TodoStore")], getInitialState: function() { return { newTodoText: "" }; }, getStateFromFlux: function() { var flux = this.getFlux(); return flux.store("TodoStore").getState(); }, // ...省略... });
Store/Actionをrequireしなくても、
- Storeは
this.getFlux().store('TodoStore')
- Actionは
this.getFlux().actions
という風にアクセスできる。
↑のコードではStoreWatchMixinも使ってる。
StoreWatchMixinを使うと、指定したStoreでemit('change')
された時、勝手にgetStateFromFlux()
してくれる。
Dispatcherいらない
FluxにはDispatcher
っていう部品がある。
Dispatcherは、ActionCreatorで発行されたActionをStoreに通知する役割。
アプリの内容にかかわらずやることが同じなので、自前で書きたくない。
FluxxorではFluxxorがやってくれるので書かなくていい。
Fluxxorの注意点
というように便利なFluxxorだけど、使ってみていくつか注意することあった。
FluxxorというよりFluxの問題かもだけど
既存コードそのまま使えない
StoreもActionも書き方変わるので、これまでに書いたコード全部修正することになる
StoreがめっちゃFatになる
はい
最上層のComponentの扱い
素のFluxだと、Component毎に必要なStoreやActionをrequireして使う。
Fluxxorでは、使用する全てのStore/ActionをFluxxor.Fluxに登録し、最上層のComponentに
React.render(<Application flux={flux} />, document.getElementById("app"));
とかって渡す。
こうする事で、子孫Componentからthis.getFlux()
を通じてStore/Actionの呼び出すことができる。
つまり、いくらComponentを細かく分けても、ページ全体が大きくなればなるほど、最上層のComponentのコードもどんどんでかくなってしまう。
StoreWatchMixinで監視できるのはchangeイベントのみ
素のFluxだと、Store内で適当なイベント発行して、イベント毎にaddEventListenerしたりできた。
FluxxorでStoreWatchMixinでStoreを監視するとき、changeイベント以外は監視してくれない。
Fluxxorで書くときは、changeイベント以外使わない設計にした方が良さそう。
感想
Reactはカッチリした世界観だけど、素のFluxは大分自由な感じで頭が混乱する。
Fluxxorは部品の作り方をある程度決めてくれるので気が楽になる。
Fluxのフレームワークには Delorean っていう競合製品もあるので、Fluxで書きたいけどFluxxorは嫌な人は触ってみてもいいかも。
次はmizchiさんの記事です。