Flummoxでasync/awaitを使ってQiitaのリストを取得してみた。

# Flummoxでasync/awaitを使ってQiitaのリストを取得してみた。

## 導入にあたって

最近FluxをES5記法で書いた[プロダクト](https://gunosy.co.jp/news/43)をリリースして、次なるSPAプロダクトのプロジェクトにアサインされたのをきっかけにES6, ES7記法でFluxをと色々試していました。

そこでFluxのDisptcherとStore/Action周りをすっきり書けないものかと調べたところ、[Flummox](http://acdlite.github.io/flummox)か[material-flux](https://github.com/azu/material-flux)かで迷い、結局ドキュメントや導入事例が多そうな雰囲気のあるFlummoxにしました。

### Webpackの設定

Webpackを今回は使用しています。loaderはこんな感じ。

```
{
  test: /\.js$|\.jsx?$/,
  exclude: /node_modules/,
  loader: 'babel?optional[]=runtime&stage=0'
}
```

### Babelについて

Babelはver 5以上にしておいたほうがよいです。あと `babel-runtime` が必要。

```
"babel": "^5.6.14",
"babel-core": "^5.6.17",
"babel-loader": "^5.3.1",
"babel-runtime": "^5.6.17",
```
<br><br>

## Example

今回はサンプルとして、QiitaのIndexを表示して、検索結果を返すものをつくってみました。

目的としては以下。

- APIを[whatwg-fetch](https://github.com/github/fetch)で叩いてリストに表示する。ここでasync/awaitを使う。
- FluxをFlummoxで実装する

**[GitHub/flummox-es7-example](https://github.com/tyshgc/flummox-es7-example)**

1. git clone後、 `npm install` してください。warnが出る場合は `sudo npm install`
2. install後は、 `$ gulp` でbuild&watch開始します。
3. iOS SafariかAndroid Chromeで `http://localhost:3000` にアクセスしてみてください。


※スマートフォンブラウザ用につくっているので、デスクトップブラウザでは見ないほうが良いです。特にIE。まぁ多分見れますけど。
※Android 4.3以下はどうなるか知らないです。見れるとは思うけど。 


#### ディレクトリ構成

```
- stores/
- actions/
- constants/
- views/
	- components/	// 共用コンポーネント
	- modals/		// モーダルコンポーネント
	- pages/		// ページコンポーネント
```


## **Action**


```
/**
 * Action: QiitaIndexListActions - QiitaのIndexリストを管理するAction
 */
import { Actions } from 'flummox';
import Fetcher from '../utils/fetcher';

export default class QiitaIndexListActions extends Actions {
  
  /**
   * Indexリストを渡す
   */
  async getIndexList(option){
    
    let fetchUrl = 'http://qiita.com/api/v2/items';
    
    /**
     * @page: ページ番号 (1から100まで)
     * @per_page: 1ページあたりに含まれる要素数 (1から100まで)
     * @query: 検索クエリ
     */
    let queryData = {
      page: 1,
      per_page: 10,
      query: null
    };
    
    if(option){
      queryData.page = option.page || 1;
      queryData.per_page = option.per_page || 10;
      queryData.query = option.query || null;
    };
    
    let fetchedData = await new Fetcher(
      fetchUrl,
      {
        queries: queryData
      })
      .then((json)=> {
        return json;
      });
    
    return await fetchedData;
  }
  
  setSearchQuery(searchQuery) {
    if(!searchQuery) return;
    this.getIndexList({
      query: searchQuery
    })
  }
  
}
```

ActionのメソッドをStore側へ後に紐付けします。紐付けしていればreturnで値をStoreのメソッドへ自動で渡っていきます。

ActionではQiita APIから非同期でfetchするようにしています。ここでES7のasync/awaitを使っています。

##### **asyncでラップしている中で、Promiseをキャッチする仕組みで順番としてこうなっている。**

1. `Fetcher()` *fetch()のCustom Wrapper* でfetchを走らせる。
2. APIを叩き終わって戻り値 `json` を返す。
3. 値が返ってきた後、`fetchData` をStoreのメソッドへ渡して `Fetcher()` 終了。
4. Storeのハンドラが処理を開始。

##### **async/awaitなしだと**

1. `Fetcher()` でfetchを走らせる。
2. fetchDataをreturnする(当然空なので何も渡せない)。
3. Storeのハンドラが処理を開始（nullはスルー）。
4. APIを叩き終わって `json` を返すがそこで `Fetcher()` の処理は終了。

async/awaitなしだと、Promiseで処理しても、Storeへ値を渡すコードが冗長的な雰囲気。
例えば、Storeへ渡すメソッドを別メソッドにして、 `.then(json=> json)` の部分呼び出せば可能だと思います。


<br><br>

## **Store**


```
/**
 * Store: QiitaIndexListStore - QiitaのIndexリストを保持
 */
import {Store} from 'flummox';

/**
 * @list: QiitaのIndexリスト
 */
export default class QiitaIndexListStore extends Store {
  constructor(flux) {
    super();
    
    const ids = flux.getActionIds('QIITA_INDEX_LIST');
    this.register(ids.getIndexList, this.handleGetIndexList);
    
    this.state = {
      list: null
    };
  }
  
  /**
   * Index Listを受け取りsetStateして該当Componentへprops伝播
   */
  handleGetIndexList(list) {
    if(!list) return;
    
    this.setState({
      list: list
    });
  }
}
```

Storeでは、ActionのId(Key)からActionのMethodを取得して、 `this.register()` でStoreにあるHandlerと結びつける必要があります。

```
const ids = flux.getActionIds('CONPASS_INDEX_LIST');
this.register(ids.getIndexList, this.handleGetIndexList);
```

1. ActionのgetIndexList()で値を渡すと、Store側がそれを監視してHandlerに処理や値を受け取ります。
- Handler内でthis.setState()すると、関係しているComponentへpropsとして値を伝播させます。
	- 関係するComponentについては後ほど説明します。


<br><br>


## Constants(Flux)

Action/Storeをkeyで紐付けます。

```
import {Flummox} from 'flummox';

/* Actions */
const {
  QiitaIndexListActions
} = require('../actions');

/* Store */
const {
  QiitaIndexListStore
} = require('../stores');

class Flux extends Flummox {
  constructor() {
    super();
    
    /**
     * QiitaIndex - Qiitaのリスト管理
     */
    this.createActions(
      'QIITA_INDEX_LIST', 
      QiitaIndexListActions
    );
    this.createStore(
      'QIITA_INDEX_LIST', 
      QiitaIndexListStore, 
      this
    );
  }
}

module.exports = new Flux();
```

今回はStore/Actionともに一つしかないのですが、Storeが増えたらここで紐付けを新たに行う。
こんな感じに…

```
    /**
     * QiitaIndex - Qiitaのリスト管理
     */
    this.createActions(
      'QIITA_INDEX_LIST', 
      QiitaIndexListActions
    );
    this.createStore(
      'QIITA_INDEX_LIST', 
      QiitaIndexListStore, 
      this
    );

    /**
     * QiitaSummary - Qiitaの記事サマリー管理
     */
    this.createActions(
      'QIITA_SUMMARY', 
      QiitaSummaryActions
    );
    this.createStore(
      'QIITA_SUMMARY', 
      QiitaSummaryStore, 
      this
    );

```

<br><br>

## View(React Component)

ここからはおなじみのReact.jsです。

### QiitaIndex.jsx: リスト用のコンポーネント

部分的に説明します。
まず、 `constructor()` で Store/Actionを呼び出します。とくにStoreにあるデータにアクセスする必要がない場合は、Actionのみ呼び出しで良いです。

```

  constructor(props) {
    super(props);
    
    this.QiitaIndexListActions = this.props.flux.getActions('QIITA_INDEX_LIST');
    this.QiitaIndexListStore = this.props.flux.getStore('QIITA_INDEX_LIST');
    
    this.state = {};
    this.text = {};
  }

```

`componentDidMount()` （Componentが初回生成されたタイミング）でQiitaのリストデータを取得します。

```
  componentDidMount() {
    this.onGetIndexListHandler();
  }
  
  onGetIndexListHandler() {
    this.QiitaIndexListActions.getIndexList();
  }
```

`this.QiitaIndexListActions.getIndexList()` これはActionのメソッドなので、Actionの動きを見てみると…

```
  async getIndexList(option){
    
    let fetchUrl = 'http://qiita.com/api/v2/items';
    
    /* ... 省略 ... */
    
    let fetchedData = await new Fetcher(
      fetchUrl,
      {
        queries: queryData
      })
      .then((json)=> {
        return json;
      });
    
    return await fetchedData;
  }

```

そして、 `return await fetchedData` でStoreの以下メソッドに渡します。

```
  /**
   * Index Listを受け取りsetStateして該当Componentへprops伝播
   */
  handleGetIndexList(list) {
    if(!list) return;
    
    this.setState({
      list: list
    });
  }
```

ここで、`setState` され、`QiitaIndex.jsx` へpropsとしてリストデータが渡され、再度レンダリングが走ります。


<br><br>

## これだけのコードでどうやってこのデータフローを回せるのか？

答えは、以下 `App.jsx` や リストを表示している親コンポーネント（ `Top.jsx` : ページコンポーネント ）にあります。

### App.jsx

```
/**
 * Root Component: App
 */
import React from 'react';
import { Router, RouteHandler } from 'react-router';
import joinClasses from 'react/lib/joinClasses';
import FluxComponent from 'flummox/component';
import Flux from '../constants/Flux';


export default class App extends React.Component {
  
  constructor(props) {
    super(props);
    
    this.state = {};
  }
  
  render() {
    return (
      <main 
        ref='main_content'
        className={joinClasses(
          'layout_main'
        )}>

        <FluxComponent flux={Flux}>
          <RouteHandler />
        </FluxComponent>
      </main>
    );
  }
  
  static get propTypes() {
    return {};
  }
  static get defaultProps() {
    return {};
  }
  static get contextTypes() {
    return {
      router: React.PropTypes.func
    };
  }
}
```

上のコードでは、まず `FluxComponent`と `constants/Flux`をimportします。
そして、`render()` 内でReact/Routerの `RouteHandler`へFluxオブジェクトを渡すために、 `<FluxComponent>`でラップします。
こうすることにより、配下にある `<FluxComponent>` へconstantsが渡され、ひも付けているStore/Actionの情報が伝播します。

ここでは、React/Routerで `Top.jsx` (トップページ) を呼ぶようにしているので、

```
/**
 * Page Component: Top
 */
import React from 'react';
import { Router, State, Link } from 'react-router';
import joinClasses from 'react/lib/joinClasses';
import FluxComponent from 'flummox/component';

/* Components */
const {
  Search,
  QiitaIndex
} = require('../components');

export default class Top extends React.Component {
  
  constructor(props) {
    super(props);
    
    this.state = {};
    this.text = {};
  }
  
  render() {
  
    let {
      className
    } = this.props;
    
    let {} = this.text;
    
    return (
      <section 
        className={
          joinClasses("Page-Top", className)
        }>
        <FluxComponent connectToStores={['QIITA_INDEX_LIST']}>
          <Search />
        </FluxComponent>
        <FluxComponent connectToStores={['QIITA_INDEX_LIST']}>
          <QiitaIndex />
        </FluxComponent>
      </section>
    );
  }
  
  static get propTypes() {
    return {};
  }
  
  static get defaultProps() {
    return {};
  }
  
}
```

該当コンポーネントを `<FluxComponent connectToStores={['QIITA_INDEX_LIST']}>` でラップします。
例えば、`<QiitaIndex />` の配下にActionやStoreの処理がある場合は、 `connectToStores={['QIITA_INDEX_LIST']}` のアトリビュートはナシで良いです。Storeと接続したい場合のみこのアトリビュートを付与します。

```
<FluxComponent>
 <QiitaIndex />
</FluxComponent>

```

ちなみに、 `<Search />` では、フォームに入力した検索クエリをActionで渡して、fetchを発火させています。Infinit Scrollなども同じような要領でできますが、面倒くさかったので今回はいれていません。


<br><br>


## 所感

Fluxに比べてよりデータフローがわかりやすい気がしました。
動きとしては、Flux同様に `Action → Dispacher → Store → View → Action` ですが、Fluxは最初に実装するときに関連性がなかなか追いにかったのですが（どこをどの順番で見ていいのかわかりにくい）、そこは幾分楽になった感はありました。

async/awaitはコードのリーダビリティが上がった感がありました。BabelのLoaderで少しハマりました。Babel関連のバージョンには注意が必要です。