|> Weeeeeeb

どんどんドーナツどーんと行こう!(10万円山さん)

Day.1 Flux入門

React.jsは以前触っていましたが、Fluxのアーキテクチャについては不勉強だったため、
この機会に入門してみる。

まずはということで簡単なアプリを作ります。
今回勉強のために使わせていただいたのは下記のスライド↓(ありがとうございます)

10分で実装するFlux

私の方でもほぼ同一のものを作成しました。

github.com

アーキテクチャ

Fluxのアーキテクチャです。
React.jsだけを勉強していた私でも見たことがある有名な図ですね。
図から自明ですが、データの流れが一方向になっています。(Angularとかは双方向でしたよね)

https://facebook.github.io/react/img/blog/flux-diagram.png

Component

Viewですね。ここでAction(イベント)を呼び出します。
Store(state)が更新されたタイミングでViewが更新されます。
ユーザーは更新を意識する必要はありません。

Action

ここでは、システム的なイベントやユーザー入力(onClick等)等の管理を行います。
この後、Dispatcherへデータを渡します。
その他外部(API)との通信等も行います。

Dispatcher

DispatcherはFluxで提供されているモジュール。
Fluxの中ではコアな部分?
ここで、事前にStoreに登録されているコールバックを呼び出す。

本来はEventEmitterを内包しているのがDispatcherで、
EventEmitterの制御を行っているとのこと。

私自身EventEmitterは全然知らなくて、参考にしている記事からの情報しか知らないので
AdventCalendarのどこかで取り上げたい…

Store

Storeは基本的にデータの管理(state)を行います。
また、ビジネスロジックを記述する部分です。

またはStoreはSetterを持たず、コールバックにて、値の代入などを行います。(Getterはある)

処理に関して

私の理解で記述したファイルの処理を書きます。
自分のためなんじゃ…

※下記にプログラムを掲載しますが、参考記事様のコードとほぼほぼ同一なので
そちらを参照した方がよいかもしれません。

Component -> (Component.js)

今回作成したのは簡単なカウンターなので、
必要なViewは下記の2点のみです。

  • カウントするボタン
  • カウントを表示するラベル

constructor

  • stateの初期値をStoreから取得
  • Storeからのコールバックを登録(このタイミングで再描画)

tickメソッド

  • Actionの呼び出し
"use strict";
import React from "react"
import ActionCreator from "./ActionCreator"
import Store from "./Store"
import EventEmitter from "./EventEmitter"

var dispatcher = new EventEmitter();
var action = new ActionCreator(dispatcher);
var store = new Store(dispatcher);

export default class Component extends React.Component {
    constructor(props) {
        super(props);
        this.state = { count: store.getCount() };
        // Observe store's change
        store.on("CHANGE", () => {
            this._onChange();
        });
    }
    _onChange() {
        this.setState({ count: store.getCount() });
    }
    tick() {
        action.countUp(this.state.count + 1);
    }
    render() {
        return (
            <div>
                <button onClick={this.tick.bind(this)}>Count Up</button>
                <p>
                Count: {this.state.count}
                </p>
            </div>
        );
    }
}

Action -> (ActionCreator.js)

ここではcountUpメソッドが呼び出された際に、
Dispatcherにデータ全て横流ししています。

"use strict";
export default class ActionCreator {
    constructor(dispatcher) {
        this.dispatcher = dispatcher;
    }
    countUp(data) {
        this.dispatcher.emit("countUp", data);
    }
}

Dispatcher -> (EventEmitter.js)

下記の指示を行うためのプロセスを登録

  • カウンター値の更新
  • Viewの更新
"use strict";
export default class EventEmitter {
    constructor() {
        this._handlers = {};
    }

    on(type, handler) {
        if (typeof this._handlers[type] === 'undefined') {
            this._handlers[type] = [];
        }
        this._handlers[type].push(handler);
    }

    emit(type, data) {
        var handlers = this._handlers[type] || [];
        for (var i = 0; i < handlers.length; i++) {
            var handler = handlers[i];
            handler.call(this, data);
        }
    }
}

Store -> (Store.js)

??なぜEventEmitterを継承するんだろう…

constructor

  • 全体のデータセットを初期化(今回はカウントだけ)
  • Dispatcherをlistenするために窓口を作る(onメソッド)

onCountUpメソッド

Dispatcherから渡ってきたcountをStoreにて更新
画面を更新指示出しを行っている(emit)

"use strict";
import Emitter from "./EventEmitter"
export default class Store extends Emitter {
    constructor(dispatcher) {
        super();
        this.count = 0;

        dispatcher.on("countUp", this.onCountUp.bind(this));
    }
    getCount() {
        return this.count;
    }
    onCountUp(count) {
        this.count = count;
        this.emit("CHANGE");
    }
}

さいご

使ってみてFluxの良さわかってません。
自分で考えて作成しようと思います。
ただ、一方向のデータバインディングなのでわかりやすいし、自分のタスクに集中出来る所がいいですね。