Fluxなフレームワーク、Fluxxorの紹介

この記事はVirtual DOMアドベントカレンダーの5日目の記事です。

http://fluxxor.com/fluxxor.svg

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リストのチュートリアルある。

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さんの記事です。