React + Flux入門


この記事ではReactとFluxのアーキテクチャの概要と、実際にReactとFluxを使ってアプリを開発する方法を簡単に説明します。特にFluxにフォーカスしています。

併せて、ReactとFluxを使ったアプリを簡単に作れるようにする[starter-react-flux](https://github.com/SokichiFujita/starter-react-flux)というツールもnpmに公開しました。使い方は[こちら](https://qiita.com/sl2/items/ff7a07c00f545d245a5c#使い方)です。これを使えば、React + Flux + Babel + Webpack + ESlint + Prettier + Jest他ポピュラーなライブラリで構成されるPWAをすぐに生成できます。また、JavaScriptだけでなくTypeScriptにも対応しています。この記事は入門編なのでJavaScriptで説明しています。

# Fluxとは

Facebookが提唱したUIを持ったアプリを作るためのアーキテクチャです。アーキテクチャを実現するための同名のライブラリもあります。

## アーキテクチャとしてのFlux

Facebookは次のような図でFluxを説明しています。

![](https://facebook.github.io/flux/img/overview/flux-simple-f8-diagram-with-client-action-1300w.png)

[In-Depth Overview](https://facebook.github.io/flux/docs/in-depth-overview)

ざっくり言うと、**View**等でイベントが発生した結果**Action**が生成されて、**Dispatcher**経由で**Store**に渡り、Storeの内容が再度**View**に反映されるという図です。データの流れが**一方通行(uni-dierctional)**であることがFluxの重要な点です。

ただし、この説明だけだと、Actionは誰がどう作るのか、Storeが何なのかが明確ではありません。もう少しだけ詳しく説明すると次のようなものです。

0. **View**が、**Store**に格納された状態(state)を元に表示されます。
1. **View**でイベント(`onClick`など)が発生すると、対応するイベントハンドラ(例. `handleClick()`)が呼ばれます。
   - Viewの例: React component
2. イベントハンドラが**Action Creator**という関数を呼び出します。
   - Action Creatorが行う処理の例: APIコール、DB操作、計算など
3. **Action Creator**は、**Action**とよばれるデータ生成して、**Action**を**Dispatcher**の`disptach()`メソッドに渡します。
   - Actionの例: REST APIのレスポンス、DBから取得したデータ
4. **Store**が**Action**を受け取って、**Store**のstateと**Action**の内容を元に、stateを更新します。
5. **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

0. ActionCreatorsのファイルを作成します。
1. ユーザイベントに対応するロジックやビジネスロジックを記述します。
2. ロジックの処理結果を元にActionを作成してDispatcherに渡します。

### 用語の整理

Fluxを説明するドキュメントには、Action, Action Creator, Action Creator*s*とAction関連で似た言葉が出てきてわかり辛いので解説します。

#### Action

- ユニークな識別子とデータから構成されるオブジェクトです。

    例: 

    ```json
    {
       type: "ユニークな識別子",
       payload: データ(APIのレスポンス等)
    }
    ```

    Actionのスキーマは規定されていませんが、自由だと不便なので[Flux Standard Action](https://github.com/redux-utilities/flux-standard-action)というActionのスキーマ仕様が提案されています。[starter-react-flux](https://github.com/SokichiFujita/starter-react-flux)でもFlux Standard Actionを採用しました。

- ActionはDispatcher経由でStoreに渡されます。

- ActionCreator: Actionを生成して、ActionをDispatcherに渡す関数です。ViewのonClick等から呼び出されるケースが多いです。

- ActionCreators: 複数のActionCreatorが定義されたファイルです。

### 実例

```js
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上の実装例](https://github.com/actorapp/ActorSDK-Web/tree/master/src/actions)

## Store: ReduceStore

アプリケーションで使う状態(state)を管理します。

0. ReduceStoreを拡張したクラスを作成します。
1. stateの初期値を、`getInitialState()`関数の戻り値として定義します。
	- 例: Map, Array, String, Number, Object,..
	- 例: blogの記事一覧を格納するReduceStore -> blog記事を古い順に格納したArray
2. 「現在のstate」と「dispatcher経由で受け取ったaction」から「新たなstate」を作成する`reduce(state, action)`を実装します。  
	- 例: blogのコメント一覧の場合、「現在のコメント一覧(Array)」とActionオブジェクト内の「新たなコメント一覧(Array)」を結合したArrayを`reduce`でreturn。

### 実例

```js
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上の実装例](https://github.com/actorapp/ActorSDK-Web/search?utf8=✓&q=reducestore)

※ 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で実装します。

0. ReactComponentを作成して、`Container.create()`でContainer化します。
1. `getStores()`で、利用したいReduceStoreを指定します。
2. `calculateState()`で、ReduceStoreからstateを取得して、Containerで使う際のスキーマを定義します。

### 実例

```js
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上の実装例](https://github.com/actorapp/ActorSDK-Web/search?utf8=✓&q=container)

## View: React Component

普通のReact componentです。Containerのstateをpropsとして受け取ります。

# 付録: [starter-react-flux](https://github.com/SokichiFujita/starter-react-flux)について

## 更新履歴

- v4: 2019/3
    - Progressive Web Application (PWA)に対応
    - UIアップデート

- v5: 2019/8
    - JavaScriptとTypeScriptの両方に対応
        - `--ts`オプションをつけるとTypeScriptのプロジェクトを生成。(標準はJavaScript)
    - npmとyarnの両方に対応
        - `--yarn`オプションをつけるとyarnでライブラリをインストール。(標準はnpm)

## 使い方

1. React/Fluxアプリを作ります。

	JavaScriptの場合

	```
	npx starter-react-flux init
	```

	TypeScriptの場合
	
	```
	npx starter-react-flux init --ts
	```

2. 作成したReact+Fluxアプリを起動します。

	```
	npm start
	```

	- アプリが起動します。
	- ファイルを修正すると自動でリロードされます。

	![Screen Shot](https://raw.githubusercontent.com/SokichiFujita/starter-react-flux/master/images/app1.png)


3. ESlintで静的解析を行います。

	```
	npm run lint
	```

4. PrettierでESlintのルールを満たすようコードをある程度自動修正します。

	```
	npm run lint
	```

4. Jestでテストを実行します。

	```
	npm test
	```

5. アプリをビルドします。ビルドしたアプリはpublic配下に格納されます。

	```
	npm run build
	```