はるみちゃんのてっくぶろぐ

はるみちゃんのブログだよ。主に技術系の記事を書くよ。

React x flux でツールを作ったのでfluxについてまとめる

flux

そこら中で貼られてるfluxの概念図を貼っておきます。

f:id:deeptoneworks:20161012135719p:plain

簡単に言うと、
・Viewは親Component
・Actionは処理の内容を記したオブジェクト
・DispatcherはStoreへActionの通知を行う。具体的には登録されたCallbackを順次実行していく。
・Storeは状態の保持と状態の更新の通知を行う

View -> Action -> Dispatcher -> Storeとデータが伝搬されていく。
ViewはStoreを保持しており、Storeの状態が更新されたらViewに通知をする。

では、ひとつひとつの登場人物を詳しく見ていきます。

Dispatcher

DispatcherはActionを受け取って、登録されているCallbackを実行するものです。
Dispatcherに必要なメソッドはfluxライブラリから全て提供されていますので、私たちにに必要なことはDispatcherを使うことだけです。
使うというのは、DispatcherにCallbackを登録、Callbackを実行、Callbackが呼び出される順番を操作する、などといったことを指します。

import {Dispatcher} from "flux";
export default new Dispatcher();

Dispatcherはしばしば拡張されることがあります。
拡張にどのようなパターンがあるのかはわかりませんが、1つのパターンとして、どのVIewからdispatchされたか、という情報を付与するために用いられます。

import {Dispatcher} from "flux";
import assign from ‘object-assign’;
const dispatcher = assign(new Dispatcher(),{
    handleViewAAction: function (action) {
        this.dispatch({
            source: "viewA",
            action: action
        })
    },
    handleViewBAction: function (action) {
        this.dispatch({
            source: "viewB",
            action: action
        });
    }
});
export default dispatcher;

ViewからActionを実行するときは、dispatchメソッドの代わりに、先に拡張したメソッドを用います。
拡張していない場合は普通にdispatchメソッドを使います。

dispatcher.handleViewBAction(action);

こうすることで、どのViewからDispatchされたか、という情報を付与した上でCallbackを実行できます。
他にも拡張のパターンがあれば教えて下さい><

Action

Actionは処理の内容を記したオブジェクトをDispatcherにdispatch(発行)します。
dispatchされたらDispatcherは登録されたCallbackを実行する、という感じですね。

import dispatcher from "./dispatcher";
const Action = {
    sampleAction(data){
        dispatcher.dispatch({type:"SAMPLE_ACTION",value:data})
    }
};
export default Action;

actionを受け取るCallbackはtypeを見てデータをどう扱うかを決定します。

Store

ストアは状態を保持します。
更に、dispatcherにCallbackを登録する役割もあります。
同じstoreが複数同じCallbackを登録してはいけないので、Storeはシングルトンになります。

import { EventEmitter } from "events";
import dispatcher from "./dispatcher";
class SampleStore extends EventEmitter {
    constructor() {
        super();
        this.data = 0;
        this.token = dispatcher.register(this.handleAction.bind(this));
    }
    getAll(){
        return this.data;
    }
    emitChange(){
        this.emit("change");
    }
    addChangeListener(callback){
        this.on("change",callback);
    }
    handleAction(action) {
        switch (action.type) {
            case "SAMPLE_ACTION": {
                this.data = action.value;
                this.emit(‘change’);
            }
        }
    }
}
export default new SampleStore();

コンストラクタでdispatcherにCallbackを登録しています。このメソッドにactionが届くわけですね。

さて、登録したCallback、handleActionにactionが届きます。
この時storeはactionの型を見て、どのようにデータを扱うかを判断します。
上記の例では単純にdataにactionのvalueを入れているだけですね。
そして、データに変更があったという事をEventEmitterにより通知しています。
この通知をViewが受け取って、状態が更新されていきます。

addChangeListener(callback){
    this.on("change",callback);
}

例えば上記の例ですと、changeイベントが通知されればaddChangeListenerに登録されたcallbackが実行されるといった流れです。

View

Viewではまず、storeの初期値をコンストラクタで受け取ります。
一般にstoreではgetAllメソッドという名前で提供することが多いようです。

this.state = store.getAll();

そして、storeからデータの変更通知を受け取るcallbackを登録します。

let self = this;
store.addChangeListener(function(){
    self.setState(store.getAll());
});

これで、storeの値が変更されると、viewのstatusも変更されるようになりました。
あとはこのstatusの値を子Componentのpropsとして流し込んでやれば単一方向のデータフローが実現できますね。