はじめに
こんにちは。皆さんはReact使われてますでしょうか?
今回はReactとFluxについて書きます。
MVCの問題点
詳しくはこちらに書いていますが、MVCでアプリを作った場合は、ModelとViewの間で双方向のデーターフローが作られる可能性があるので理解したりデバックするのが難しくなります。
React単体で使った場合の問題点
子のViewでなにかアクションを起こした結果、親のViewにも変更を伝えてなににかをする場合、子のViewに親のViewのコールバック関数を渡す必要があります。
子のViewの下にさらに、子のViewがあって、アクションが起こった時に親のViewに伝えようとするとコールバックが深くなって複雑になってしまいます。
Flux
それを解決できる方法としてFluxです。
Fluxを使うとデーターフローが一方向になります。
やってることはObserverパターンです。
Fluxを実現するのに使うライブラリ
Fluxの実装自体は自前で実装してもそこまで大変ではないですが、Fluxフレームワークは何種類か出ています。
自分が調べた限りだと、現状はこのフレームワークが凄く使われてて、特にいいというのがなさそうだったので、今回は本家のFacebookのFluxを使います。
React
https://github.com/facebook/react
Viewとして使います。
Flux
https://github.com/facebook/flux
Dispatcherがライブラリとして使えるので使います。
EventEmitter3
https://github.com/primus/eventemitter3
StoreからViewに更新があったことをイベントとして通知するのに使います。
Node.jsの events.EventEmitterと同じように使えます。
EventEmitter2は高速化とブラウザでも使えるようにしたもので、EventEmitter3はさらに高速化したライブラリです。
デモ
View
src/components/item_form.js
'use strict';
import React from 'react'
import ItemActions from '../actions/item_actions'
export default class ItemForm extends React.Component {
create(event) {
let text = this.refs.itemText.getDOMNode().value;
if (text) {
ItemActions.create(text);
this.refs.itemText.getDOMNode().value = '';
}
}
render() {
return (
<div className="input-group">
<input type="text" className="form-control" ref="itemText"></input>
<span className="input-group-btn">
<button className="btn btn-primary" type="button" onClick={this.create.bind(this)}>+</button>
</span>
</div>
);
}
}
まずユーザーがフォームにテキストを入力して、+のボタンを押した時にonClickでItemActionsのcreateが呼ばれます。
Action
src/actions/item_actions.js
'use strict';
import uuid from 'uuid'
import Dispatcher from '../dispatcher/dispatcher'
class ItemActions {
constructor(dispatcher) {
this.dispatcher = dispatcher;
}
dispatch(params) {
this.dispatcher.dispatch(params);
}
create(body) {
this.dispatch({
type: 'create',
item: {id: uuid.v4(), body: body}
});
}
}
export default new ItemActions(Dispatcher);
ItemActionsのcreateはthis.dispatcherにデーターを渡します。
今回の場合はサーバーにデーターを保存しないので直接ですが、APIを叩いてサーバーにデーターを保存する場合は、Ajaxの成功した時のコールバックでthis.dispatcherを呼ぶように使います。
Dispatcher
src/dispatcher/dispatcher.js
'use strict';
import {Dispatcher} from 'flux'
export default new Dispatcher();
Dispatcherの実装はFluxのものを使うので、インスタンスを作るだけですみます。
Store
src/stores/item_store.js
'use strict';
import EventEmitter from 'eventemitter3'
import Dispatcher from '../dispatcher/dispatcher'
class ItemStore {
constructor(dispatcher) {
this.state = this.getInitialState();
this.eventEmitter = new EventEmitter();
this.dispatcher = dispatcher;
this.changeEvent = 'change';
this.dispatcher.register((action) => {
this.invokeOnDispatch(action);
});
}
getState() {
return this.state;
}
getInitialState() {
return {items: []};
}
addListener(listener) {
this.eventEmitter.addListener(this.changeEvent, listener);
}
removeListener(listener) {
this.eventEmitter.removeListener(this.changeEvent, listener);
}
emitChange() {
this.eventEmitter.emit(this.changeEvent);
}
invokeOnDispatch(action) {
switch (action.type) {
case 'create':
this.state.items.push(action.item);
this.emitChange();
break;
}
}
}
export default new ItemStore(Dispatcher);
ItemActions.createでdispatchされたデーターを受けます。
invokeOnDispatchの中でcreateの場合はthis.state.itemsにitemを一つ追加してstateが変わったことをthis.emitChange()で通知します。
View
src/components/app.js
'use strict';
import React from 'react'
import ItemStore from '../stores/item_store'
import ItemForm from './item_form'
import Items from './items'
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = ItemStore.getState();
}
componentDidMount() {
ItemStore.addListener(this.onChange.bind(this));
}
componentWillUnmount() {
ItemStore.removeListener(this.onChange.bind(this));
}
onChange() {
this.setState(ItemStore.getState());
}
render() {
return (
<div className="container">
<ItemForm />
<Items items={this.state.items} />
</div>
);
}
}
ItemStoreでemitChangeした時に呼ばれるItemStore.addListenerにonChangeを登録しておきます。
onChangeではthis.setStateでStoreから取得したデーターを元にReactがViewをレンダリングします。
ここまでがユーザが操作した結果をもとに画面の表示が変わる一通りのフローです。
データフローも一方向にできました。
リポジトリ
https://github.com/f96q/react-flux-sample
最後に
今回は説明する都合上、最小のコードをサンプルを載せましたが、実際のWebアプリのサンプルとしては、リモートでふりかえりができるWebサービスのKPTBoardを最近、ReactとFluxで書き換えましたので興味がある方は参照下さい。
https://github.com/f96q/kptboard
宣伝
DocBaseは情報共有を活発にしてチームが力を最大限発揮できるようにと思って作ったサービスです。今回の記事で書いたように試行錯誤を繰り返して作っています。弊社のような開発をする組織には便利に使えるように作っていますので、ぜひ試してみてください。
詳しくはこちらから。
https://docbase.io
このエントリーに対するコメント
日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)
- トラックバック
「いいね!」で応援よろしくお願いします!