この記事ではReactとFluxのアーキテクチャの概要と、実際にReactとFluxを使ってアプリを開発する方法を簡単に説明します。特にFluxにフォーカスしています。
併せて、ReactとFluxを使ったアプリを簡単に作れるようにするstarter-react-fluxというツールもnpmに公開しました。使い方はこちらです。これを使えば、React + Flux + Babel + Webpack + ESlint + Prettier + Jest他ポピュラーなライブラリで構成されるPWAをすぐに生成できます。また、JavaScriptだけでなくTypeScriptにも対応しています。この記事は入門編なのでJavaScriptで説明しています。
Fluxとは
Facebookが提唱したUIを持ったアプリを作るためのアーキテクチャです。アーキテクチャを実現するための同名のライブラリもあります。
アーキテクチャとしてのFlux
Facebookは次のような図でFluxを説明しています。
ざっくり言うと、View等でイベントが発生した結果Actionが生成されて、Dispatcher経由でStoreに渡り、Storeの内容が再度Viewに反映されるという図です。データの流れが**一方通行(uni-dierctional)**であることがFluxの重要な点です。
ただし、この説明だけだと、Actionは誰がどう作るのか、Storeが何なのかが明確ではありません。もう少しだけ詳しく説明すると次のようなものです。
- Viewが、Storeに格納された状態(state)を元に表示されます。
- 
Viewでイベント(onClickなど)が発生すると、対応するイベントハンドラ(例.handleClick())が呼ばれます。- Viewの例: React component
 
- イベントハンドラがAction Creatorという関数を呼び出します。
- Action Creatorが行う処理の例: APIコール、DB操作、計算など
 
- 
Action Creatorは、Actionとよばれるデータ生成して、ActionをDispatcherのdisptach()メソッドに渡します。- Actionの例: REST APIのレスポンス、DBから取得したデータ
 
- StoreがActionを受け取って、StoreのstateとActionの内容を元に、stateを更新します。
- Storeの状態(state)が、再びViewに反映されます。
ライブラリとしてのFlux
FacebookはFluxアーキテクチャを実現するためのライブラリをアーキテクチャと同じFluxという名前で提供しています。FacebookのFluxライブラリは、Fluxアーキテクチャに登場するDispatcher, Store, Viewに対応するコンポーネントを提供しています。
| Fluxの要素名 | 対応ライライブラリ名 | 
|---|---|
| Dispatcher | Dispatcher | 
| Store | ReduceStore | 
| View | Container, React Component | 
注: Fluxはv2.1.0からReduceStoreというStoreのコンポーネントとContainerというViewのコンポーネントが追加されました。これらは大変強力なコンポーネントで、ReduceStoreの状態をContainerに自動で反映してくれます。ReduceStore登場以前は、Storeの変更をViewに反映させるためにEvent Emitterが必要で記述が多く面倒でしたが、現在ではそのような処理は不要です。
Fluxの実装方法
基本的に以下のファイルを実装していきます。
- ActionCreators: 何らかの処理を行い、結果からActionというデータを作り出して、Dispatcherに渡します。
- Dispatcher: 渡されたActionをStoreに配信するHub的な役割です。Actionに関する統一的な処理を行う際などにコードを追加する事はありますが、普段はあまり気にすることはないかもしれません。
- ReduceStore: アプリの状態を管理する非永続的なデータストアです。現在のストアの状態と新たなActionを元に新たな状態を作ります。
- Component: 単なるReactコンポーネントです。
- Container: ReduceStoreのデータを自動で受け取るだけの形式的なReact Componentです。アプリのロジックを書くことは推奨されていません。
Fluxの単方向データフローのデータ観点でこれを整理すると以下のような流れです。
React Component 
---[ event  ]---> ActionCreator
---[ action ]---> Dispatcher 
---[ action ]---> ReduceStore 
---[ state  ]---> Container 
---[ props  ]---> React Component 
さて、次はFluxの具体的な実装方法を要素別に見ていきます。
Action
- ActionCreatorsのファイルを作成します。
- ユーザイベントに対応するロジックやビジネスロジックを記述します。
- ロジックの処理結果を元にActionを作成してDispatcherに渡します。
用語の整理
Fluxを説明するドキュメントには、Action, Action Creator, Action CreatorsとAction関連で似た言葉が出てきてわかり辛いので解説します。
Action
- 
ユニークな識別子とデータから構成されるオブジェクトです。 例: { type: "ユニークな識別子", payload: データ(APIのレスポンス等) }Actionのスキーマは規定されていませんが、自由だと不便なのでFlux Standard ActionというActionのスキーマ仕様が提案されています。starter-react-fluxでもFlux Standard Actionを採用しました。 
- 
ActionはDispatcher経由でStoreに渡されます。 
- 
ActionCreator: Actionを生成して、ActionをDispatcherに渡す関数です。ViewのonClick等から呼び出されるケースが多いです。 
- 
ActionCreators: 複数のActionCreatorが定義されたファイルです。 
実例
import AppDispatcher from '../AppDispatcher';
const NewsActionCreators = {   //ActionCreators
  fetchNews(arg1, arg2) {      //ActionCreator
    //ユーザイベント等に対応するロジックを記述
    AppDispatcher.dispatch({   //Actionオブジェクト
      type: 'ACTION_TYPE_001',  //Actionを識別する一意の文字列, 
      payload: response,          //ロジックの結果をいれる
    });
  },
};
export default NewsActionCreators;
GitHub上の実装例
Store: ReduceStore
アプリケーションで使う状態(state)を管理します。
- ReduceStoreを拡張したクラスを作成します。
- stateの初期値を、getInitialState()関数の戻り値として定義します。- 例: Map, Array, String, Number, Object,..
- 例: blogの記事一覧を格納するReduceStore -> blog記事を古い順に格納したArray
 
- 「現在のstate」と「dispatcher経由で受け取ったaction」から「新たなstate」を作成するreduce(state, action)を実装します。- 例: blogのコメント一覧の場合、「現在のコメント一覧(Array)」とActionオブジェクト内の「新たなコメント一覧(Array)」を結合したArrayをreduceでreturn。
 
- 例: blogのコメント一覧の場合、「現在のコメント一覧(Array)」とActionオブジェクト内の「新たなコメント一覧(Array)」を結合したArrayを
実例
import { ReduceStore } from 'flux/utils';
import AppDispatcher from '../dispatchers/AppDispatcher';
class NewsStore extends ReduceStore {
  getInitialState() {
    return []; //stateの初期値を定義
  }
  reduce(state, action) {
    switch (action.type) {
      case 'ACTION_TYPE_001':
		//actionの内容を新たなstateとする (action.dataはarrayと仮定)
        return action.payload; 
      case 'ACTION_TYPE_002':
		//現在のstateとactionの内容から新たなstateを作成
        return state.concat(action.data);
      default:
		//現在のstateをそのまま返す
        return state;
    }
  }
}
export default new NewsStore(AppDispatcher);
GitHub上の実装例
※ FacebookのFlux(flux/utils)では、Storeを実現するクラスとしてStore, ReduceStore, MapStoreの3種類を用意していますが、Storeは煩雑でMapStoreはv3.0.0で削除されたのでReduceStoreだけを考えれば良いです。
View: Container
ReactComponentの一種です。ただし、ReduceStoreのstateの内容を取得して配下のReactコンポーネントに渡すだけの特殊なReact Componentです。UIやロジックをContainerの中で実装するのは非推奨で、UIやロジックは子供のReactComponentで実装します。
- ReactComponentを作成して、Container.create()でContainer化します。
- 
getStores()で、利用したいReduceStoreを指定します。
- 
calculateState()で、ReduceStoreからstateを取得して、Containerで使う際のスキーマを定義します。
実例
import React, { Component } from 'react';
import { render } from 'react-dom';
import { Container } from 'flux/utils';
import NewsStore from './stores/NewsStore';
import FavStore from './stores/FavStore';
class _App extends Component {
  static getStores() {
    return [NewsStore, FavStore]; //利用したいReduceStore
  }
  static calculateState() {
    return { //container内で`this.state.KEY_NAME`でアクセス可能
      news: NewsStore.getState(),
      favs: FavStore.getState(),
    };
  }
  render() {
	//通常のReactComponentのように書く
  }
}
const AppContainer = Container.create(_App);
render(<AppContainer />, document.getElementById('root'));
GitHub上の実装例
View: React Component
普通のReact componentです。Containerのstateをpropsとして受け取ります。
付録: starter-react-fluxについて
更新履歴
- 
v4: 2019/3 - Progressive Web Application (PWA)に対応
- UIアップデート
 
- 
v5: 2019/8 - JavaScriptとTypeScriptの両方に対応
- 
--tsオプションをつけるとTypeScriptのプロジェクトを生成。(標準はJavaScript)
 
- 
- npmとyarnの両方に対応
- 
--yarnオプションをつけるとyarnでライブラリをインストール。(標準はnpm)
 
- 
 
- JavaScriptとTypeScriptの両方に対応
使い方
- 
React/Fluxアプリを作ります。 JavaScriptの場合 npx starter-react-flux initTypeScriptの場合 npx starter-react-flux init --ts
- 
作成したReact+Fluxアプリを起動します。 npm start- アプリが起動します。
- ファイルを修正すると自動でリロードされます。
 
- 
ESlintで静的解析を行います。 npm run lint
- 
PrettierでESlintのルールを満たすようコードをある程度自動修正します。 npm run lint
- 
Jestでテストを実行します。 npm test
- 
アプリをビルドします。ビルドしたアプリはpublic配下に格納されます。 npm run build

