ReactJS + facebook/flux を ES6 の記述でカウンターのサンプル


## 追記：2016-01-20

改めて見たら冗長・間違ってた・アプデで動かないとかだったので、テスト書く勉強のついでにリファクタした。

- [Reactjs のテストを ava + jsdom で書いてみる](http://qiita.com/cortyuming/items/cad3aa5aeb658790f4e7)

下記は記録的にとりあえず残しておく。

## 目的

[前回の続き](http://qiita.com/cortyuming/items/ffbb4dde971b921d92c1)

class などを使って書き換えてみる。

## ファイル構成

```
.
├── dest
│   └── app.js
├── index.html
├── package.json
└── src
    ├── app.jsx
    └── app_dispatcher.js
```

## package


```pacage.json
{
  "devDependencies": {
    "babelify": "^6.1.2",
    "browserify": "^10.2.4",
    "watchify": "^3.2.3"
  },
  "scripts": {
    "watch": "watchify -t babelify src/app.jsx -o dest/app.js -v"
  },
  "dependencies": {
    "flux": "^2.0.3",
    "react": "^0.13.3"
  }
}
```

```
$ npm install
$ npm run watch
```

## code

```src/app.jsx
import {EventEmitter} from 'events';
import React from 'react';
import AppDispatcher from './app_dispatcher';

const COUNTER_CONST = {
    UPDATE_COUNTER: 'UPDATE_COUNTER'
};

// Store
let SINGLETON = Symbol();
let SINGLETON_ENFORCER = Symbol();


class CounterStoreSingleton extends EventEmitter {
    constructor(enforcer) {
        super();

        if (enforcer !== SINGLETON_ENFORCER) {
            throw "Cannot construct singleton";
        }

        AppDispatcher.register(this._onAction.bind(this));

        this.counter = 0;
        this.CHANGE_EVENT = 'change';
    }

    static get instance() {
        if (!this[SINGLETON]) {
            this[SINGLETON] = new CounterStoreSingleton(SINGLETON_ENFORCER);
        }
        return this[SINGLETON];
    }
    _onAction(action) {
        switch(action.actionType) {
            case COUNTER_CONST.UPDATE_COUNTER:
                CounterStore.onUpdateCounter(action.count);
                CounterStore.emitChange();
                break;
            default:
                // no op
        }
    }
    emitChange() {
        this.emit(this.CHANGE_EVENT);
    }
    updateChangeListener(callback) {
        this.on(this.CHANGE_EVENT, callback);
    }
    getCount() {
        return {count: this.counter};
    }
    onUpdateCounter(count) {
        this.counter += count;
    }
}
let CounterStore = CounterStoreSingleton.instance;


// Action
let CounterActions = {
    plusCounter: function() {
        AppDispatcher.dispatch({
            actionType: COUNTER_CONST.UPDATE_COUNTER,
            count: 1
        });
    },
    minusCounter: function() {
        AppDispatcher.dispatch({
            actionType: COUNTER_CONST.UPDATE_COUNTER,
            count: -1
        });
    }
};


// View
class CounterAppView extends React.Component {
    constructor() {
        super();
        this.state = CounterStore.getCount();
        this._onChange = this._onChange.bind(this);
    }
    componentDidMount() {
        CounterStore.updateChangeListener(this._onChange);
    }
    render() {
        return (
            <div>
                <span>count: {this.state.count}</span>
                <CounterView />
            </div>
        );
    }
    _onChange() {
        this.setState(CounterStore.getCount());
    }
}

CounterAppView.propTypes = {
    count: React.PropTypes.number
};


class CounterView extends React.Component {
    render() {
        return (
            <div>
                <div>
                    <button onClick={this.onClickPlus}>+1</button>
                    <button onClick={this.onClickMinus}>-1</button>
                </div>
            </div>
        );
    }
    onClickPlus() {
        CounterActions.plusCounter();
    }
    onClickMinus() {
        CounterActions.minusCounter();
    }
}


React.render(
    <CounterAppView />,
    document.getElementById('app-container')
);
```

```src/app_dispatcher.js
var Dispatcher = require('flux').Dispatcher;

module.exports = new Dispatcher();
```

```index.html
<!doctype html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>Flux</title>
    </head>
    <body>
        <div id="app-container"></div>

        <script src="dest/app.js"></script>
    </body>
</html>
```

## 表示

```
$ python -m SimpleHTTPServer 8000
```

- http://localhost:8000


![スクリーンショット 2015-07-03 0.20.12.png](https://qiita-image-store.s3.amazonaws.com/0/41656/3fb3784f-2d3e-965e-eff8-33d1bc7e8d04.png "スクリーンショット 2015-07-03 0.20.12.png")


## 感想

- Store の class 化は下記を参考にインスタンスを返すようにした
  - http://akabeko.me/blog/2015/04/babelify-es6/
- setState がうまく出来なくてハマったけど下記を参考にしたらできた
  - http://www.newmediacampaigns.com/blog/refactoring-react-components-to-es6-classes
- EventEmitter を理解せずに書いてたら emit や on のところでハマりかけた。 下記をを参考にした。
  - http://yosuke-furukawa.hatenablog.com/entry/2013/12/28/145647
  - http://nodejs.jp/nodejs.org_ja/api/events.html#events_class_events_eventemitter
- github の facebook/flux の example の AppDispatcher.js のコードが数か月前と書き方が変わりシンプルになっている。一部 Action に移動になってたり。なので解説記事があまり参考にならなかったりする...。
- react-mixin を使って LinkedStateMixin を使ってみようかと思ったけど断念した...
  - https://github.com/brigand/react-mixin
  - http://qiita.com/enu-kuro/items/d21aeff1f3c11cda008f#linkedstatemixin%E3%81%AE%E4%BE%8B 


## 参考

- http://gihyo.jp/magazine/wdpress/archive/2015/vol87
- https://github.com/facebook/flux/