この記事では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 init
TypeScriptの場合
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