概要
Fluxアーキテクチャを勉強しようと思って簡単なcounterを作ってみました。
動作ページはこちら
環境
- React.js 15.0.1
- Babel 6.5.2
- webpack 1.12.15
役割
担当 | 役割 |
---|---|
View | 画面の表示。ユーザイベントをActionへ通知 |
Action | アクションタイプ属性を持たせてDispatcherへ通知。外部APIへのリクエストはここ。 |
Dispatcher | Storeへ渡す |
Store | データの保持や実処理を行う。変更をViewへ通知 |
ディレクトリ構造
. ├── actions │ └── action.js ├── components │ ├── app.jsx │ ├── count.jsx │ └── display.jsx ├── counter.jsx ├── dispatcher │ └── dispatcher.js └── stores └── store.js
実装
action
import CounterDispathcer from '../dispatcher/dispatcher'; var CounterActions = { increment: function() { CounterDispathcer.dispatch({ actionType: 'increment' }); }, decrement: function() { CounterDispathcer.dispatch({ actionType: 'decrement' }); } } module.exports = CounterActions;
ポイント
- アクションタイプを持たせてDispatcherへ送る
dispatcher
var Dispatcher = require('flux').Dispatcher; module.exports = new Dispatcher();
store
import CounterDispatcher from '../dispatcher/dispatcher'; import Events from 'events'; import assign from 'object-assign'; var EventEmitter = Events.EventEmitter; var count = { value: 0 }; function increment() { count.value++; } function decrement() { count.value--; } var CounterStore = assign({}, EventEmitter.prototype, { getAll: function() { return count; }, emitChange: function() { this.emit('change'); }, addChangeListener: function(callback) { this.on('change', callback); }, removeChangeListener: function() { this.removeListener('change', callback); } }); CounterDispatcher.register((action) => { switch(action.actionType) { case 'increment': increment(); CounterStore.emitChange(); break; case 'decrement': decrement(); CounterStore.emitChange(); break; default: } }); module.exports = CounterStore;
ポイント
- アクションタイプをswitchで振り分け、処理を実行する
- Viewへイベントを通知する。
- Storeには
events
モジュール必要 - Storeにはグローバルオブジェクトができる
view
app.jsx
import React from 'react'; import CounterStore from '../stores/store'; import CounterDisplay from './display.jsx'; import CounterCount from './count.jsx'; class CounterApp extends React.Component { constructor(props) { super(props); this.state = CounterStore.getAll(); } componentDidMount() { CounterStore.addChangeListener(this._onChange.bind(this)); } componentWillUnmount() { CounterStore.removeChangeListener(this._onChange.bind(this)); } _onChange() { var v = CounterStore.getAll(); this.setState(v); } render() { return ( <div> <CounterDisplay data={this.state.value} /> <CounterCount /> </div> ); } } module.exports = CounterApp;
ポイント
- Storeからの通知を受け取る形に実装する。
- getInitialStateは使わずconstructorで
this.state = xxx
state
に入れるのはobjectじゃないとだめ- .jsxのときはimport時も.jsxを付ける
- Componentをmodule.exportsするときはclassのまま。newしない
- thisをbindする必要がある http://stackoverflow.com/questions/33631482/react-this-is-null-in-event-handler
count.jsx
import React from 'react'; import CounterActions from '../actions/action' class CounterCount extends React.Component { render() { return ( <div> <button onClick={this._increment}>+1</button> <button onClick={this._decrement}>-1</button> </div> ); } _increment() { CounterActions.increment(); } _decrement() { CounterActions.decrement(); } } module.exports = CounterCount;
display.jsx
import React from 'react'; var ReactPropTypes = React.PropTypes; class CounterDisplay extends React.Component { constructor(props) { super(props); } render() { return ( <div> <h1>{this.props.data}</h1> </div> ); } } CounterDisplay.propTypes = { data: ReactPropTypes.number.isRequired }; module.exports = CounterDisplay;