React/FluxフレームワークAltのサンプルアプリを読んでみた所感


React.jsの基本もFluxの概念もわかったけど、Fluxはフレームワーク乱立していてわからない...といった状況なんですが

[We Compared 13 Top Flux Implementations — Medium](https://medium.com/social-tables-tech/we-compared-13-top-flux-implementations-you-won-t-believe-who-came-out-on-top-1063db32fe73)
 
こういった比較記事を見ると、

- helperが充実してる
- ES5/ES6対応
- React以外のライブラリと共存できる
- コミュニティーが活発

などの理由からAltをオススメしていました。確かにstar数も1500を越えていてcommitも活発なので、試してみる価値あり、ということでまずはAltやそのサンプルコードを読んで実際の使い勝手を見ておいこうと思います。

今回読むサンプルはこちら：
[RookieOne/react-alt-github-example](https://github.com/RookieOne/react-alt-github-example)

# ローカルでサンプルが動くようにする

まずはgit clone.

```
git clone git@github.com:RookieOne/react-alt-github-example.git
```

ディレクトリを移動し、packageをインストール

```
npm install
```

次にbowerでライブラリたちを入れますが、bowerが入ってなかったので、

```
npm install bower -g
```
でbowerをゲット。

bowerからassetsをinstall。
```
 bower install
```

glupもなかったのでinstall

```
npm install -g gulp
```

して実行。

```
$ gulp
[19:36:37] Using gulpfile ~/work/react/react-alt-github-example/gulpFile.js
[19:36:37] Starting 'move-webpages'...
[19:36:37] Starting 'bower-css'...
[19:36:37] Starting 'bower-js'...
[19:36:37] Starting 'fonts'...
[19:36:37] Starting 'fonts-awesome'...
[19:36:37] Starting 'css'...
[19:36:37] Starting 'images'...
[19:36:37] Starting 'react'...
[19:36:37] Finished 'images' after 36 ms
[19:36:37] Finished 'move-webpages' after 94 ms
[19:36:37] Finished 'css' after 95 ms
[19:36:37] Finished 'bower-js' after 197 ms
[19:36:37] Finished 'bower-css' after 208 ms
[19:36:38] Finished 'fonts-awesome' after 1.11 s
[19:36:39] Finished 'fonts' after 1.55 s
[19:36:41] Finished 'react' after 3.56 s
[19:36:41] Starting 'build-app'...
[19:36:41] Finished 'build-app' after 13 μs
[19:36:41] Starting 'webserver'...
[19:36:41] Webserver started at http://localhost:8000
[19:36:41] Finished 'webserver' after 15 ms
[19:36:41] Starting 'watch'...
[19:36:41] Finished 'watch' after 11 ms
[19:36:41] Starting 'default'...
[19:36:41] Finished 'default' after 13 μs
....
```

(gulpってなんだよって人はこちらから [【Gulp.js入門】新鋭フロンエンド・タスクランナーツール を試してみました。 ｜ Developers.IO](http://dev.classmethod.jp/tool/gulpjs-part-001/) )



としたらもう見れますので、`http://localhost:8000`で見れますので早速アクセス。

```
open http://localhost:8000
```


![スクリーンショット 2015-08-26 19.38.51.png](https://qiita-image-store.s3.amazonaws.com/0/6394/7ba3c7f1-2355-bdda-8482-9d8d14883a7b.png "スクリーンショット 2015-08-26 19.38.51.png")


検索するとアニメーションつきで表示されます。

![スクリーンショット 2015-08-26 19.39.01.png](https://qiita-image-store.s3.amazonaws.com/0/6394/0fb2cda0-8af8-cb6c-9b9e-17ff0f00ab00.png "スクリーンショット 2015-08-26 19.39.01.png")

# 処理を順番に見ていく

ということで、早速コードを見ていきます。

まずはmain.js

```javascript:app/main.js
var React = require("react")
var $ = require("jquery")
var Router = require("react-router")
var routes = require("./routes.jsx")

$(function() {
  Router.run(routes, function(Handler) {
    React.render(<Handler />, document.body);
  })
})
```

Handerというコンポーネント？をbodyに対してmountして描画。


いきなりHandlerなんてコンポーネントないぞ？となりますが、`app/routes.jsx`が怪しい。

```:app/routes.jsx
var React = require("react")
var Router = require("react-router")
var { Route } = Router
var App = require("./views/app.jsx")
var Repos = require("./views/repos.jsx")
var RepoDetails = require("./views/repo-details.jsx")

module.exports = (
  <Route handler={App}>
    <Route name="repos" handler={Repos} path="/" />
    <Route name="repo-details" handler={RepoDetails} path="/repo/:owner/:name" />
  </Route>
)
```

その前に

> var Router = require("react-router")

これをしっかり理解する。

## 寄り道: react-routerについて

[rackt/react-router](https://github.com/rackt/react-router)

```
var routes = (
  <Route handler={App} path="/">
    <DefaultRoute handler={Home} />
    <Route name="about" handler={About} />
    <Route name="users" handler={Users}>
      <Route name="recent-users" path="recent" handler={RecentUsers} />
      <Route name="user" path="/user/:userId" handler={User} />
      <NotFoundRoute handler={UserRouteNotFound}/>
    </Route>
    <NotFoundRoute handler={NotFound}/>
    <Redirect from="company" to="about" />
  </Route>
);

Router.run(routes, function (Handler) {
  React.render(<Handler/>, document.body);
});
```

最後の産業がrouterのinitializeのようなものみたい。

[react-router/overview.md at 0.13.x · rackt/react-router](https://github.com/rackt/react-router/blob/0.13.x/docs/guides/overview.md)をみると、

- templateを提供してくれる。header/footerなどを何度も宣言しなくてよい。
- 呼び出し側もスマートに書ける


ってメリットがある模様。たしかにroutesで設定しておけば、

```
  <li><Link to="app">Dashboard</Link></li>
```

とするだけで http://host/appのようにroutingで指定したページを描画してくれる。


main.jsに戻ると、react-routerをつかって、どのtemplateファイル(railsでいうならlayoutsファイル)を元にroutingを行うかの定義をしているくらいだということがわかりました。

```javascript:app/main.js
var React = require("react")
var $ = require("jquery")
var Router = require("react-router")
var routes = require("./routes.jsx")

$(function() {
  Router.run(routes, function(Handler) {
    React.render(<Handler />, document.body);
  })
})
```

routes.jsxを見ると

```
var React = require("react")
var Router = require("react-router")
var { Route } = Router
var App = require("./views/app.jsx")
var Repos = require("./views/repos.jsx")
var RepoDetails = require("./views/repo-details.jsx")

module.exports = (
  <Route handler={App}>
    <Route name="repos" handler={Repos} path="/" />
    <Route name="repo-details" handler={RepoDetails} path="/repo/:owner/:name" />
  </Route>
)
```
とあり、appをtemplateファイルとして、reposとrepos-detailsの2つが登録されている。

ということで次はapp.jsxを見る。まずはviewの構成を見ます。

```javascript:app.jsx
var React = require("react")
var RouteHandler = require('react-router').RouteHandler
var Header = require("./header.jsx")

module.exports = React.createClass({
  render() {
    return (
      <div>
        <Header />
        <div className="container">
          <RouteHandler />
        </div>
      </div>
    )
  }
})

```

Headerコンポーネントが共通であるだけで、あとはroutingされたviewが描画される模様。最初はpath="/"のreposが描画されるはずなのでそちらを。


```javascript|repos.jsx

var React = require("react/addons")
var superagent = require("superagent")
var RepoCard = require("./repo-card.jsx")
var ReposStore = require("../stores/repos-store.js")
var ReposActions = require("../actions/repos-actions.js")

module.exports = React.createClass({
  mixins: [React.addons.LinkedStateMixin],
  getInitialState() {
    return ReposStore.getState()
  },
〜〜〜〜〜〜
  render() {
    return (
      <div>
        <form onSubmit={this.search}>
          <div className="input-field">
            <label>Search GitHub Repos</label>
            <input type="text" ref="searchText"  />
          </div>
        </form>

        { this.renderLoading() }

        <div className="row">
          { this.renderRepos() }
        </div>
      </div>
    )
  }
})
```

viewはシンプルで、検索フォームとその結果がrenderRepos()で描画されるであろうdivがあります。

this.renderRepos()はなにをするかというと

```javascript:repos.jsx
〜〜〜〜〜
  renderRepos() {
    return this.state.repos.map((repo) => {
      return (
        <RepoCard repo={repo} />
      )
    })
  },
〜〜〜〜〜
```

というふうに、RepoCardコンポーネントをreposの数だけ描画しています。mapはjsxでloopさせるメソッドで、アロー関数で書かれた処理を回数分やってくれる。RepoCard内部は省略するがjsxで素直にrepositoryの名前と詳細へのリンク ```<Link to="repo-details ...>``` が書かれています。


# Action/Dispather/Stores/Componentを見ていく

## AltのAction

Actionは検索ボックスを入力してsubmitすると呼ばれています。

```javascript:repos.jsx
<form onSubmit={this.search}>
```

でイベントをキャッチし、

```javascript:repos.jsx
  search(evt) {
    evt.preventDefault()
    var searchText = this.refs.searchText.getDOMNode().value
    ReposActions.search(searchText)
  }
```

で、ReposActions.searchを読んでみます。

```jsx:ReposActions.js
var alt = require("../alt-application.js")
var superagent = require("superagent")

class ReposActions {
  search(searchText) {
    superagent.get("https://api.github.com/search/repositories")
      .query({ q: searchText })
      .send()
      .end((response) => {
        this.actions.searchSuccess(response.body.items)
      })
    this.dispatch() #dispatchをcall
  }
  searchSuccess(repos) {
    this.dispatch(repos) #dispatchをcall
  }
  fetchRepo(owner, repoName) {
    superagent.get(`https://api.github.com/repos/${owner}/${repoName}`)
      .send()
      .end((response) => {
        this.actions.repoFetched(response.body)
      })
  }
  repoFetched(repo) {
    this.dispatch(repo)
  }
}

module.exports = alt.createActions(ReposActions)
```

いきなり１,2行目から見なれないファイルを読み込むが
```var alt = require("../alt-application.js")```

は

```javascript:alt-application.js
var Alt = require("alt")
var alt = new Alt()

module.exports = alt
```
altを初期化してるだけ。
次に

```var superagent = require("superagent")```

ってなんじゃとなるが（無知でごめんなさい）、

[visionmedia/superagent](https://github.com/visionmedia/superagent)
[JavaScript - jQuery.ajaxの代わりにSuperAgentを使う - Qiita](http://qiita.com/hashrock/items/3113690bb3de5bba639b)

> SuperAgent is a small progressive client-side HTTP request library, and Node.js module with the same API, sporting many high-level HTTP client features. View the docs.

とあるようにhttpクライアント。制約があってjqeuryつかえない、もしくはjqueryキライでreactせっかくつかってるのにjqueryやだなー$.getかーって人はいいですね。

で、componentのsubmitイベント時によばれたsearchですがsuperagentをつかって、githubAPIをたていて、その処理が返ってきたタイミングで```this.actions.seachSuccess```に返ってきたデータを渡してコールし、メソッド内で```this.dispatch(repos)```とdispatchにデータを渡しています。さりげなくこちらのcallbackだけじゃなく、searchメソッドの最後でも```this.dispatch()```とdispatchが呼ばれます。

「あれ、alt絡んでなくない？dispatchってどこから？」と思ったが最後に ```alt.createActions(ReposActions)```とあり、そこでactionとしての機能が付与されてるんですね。

これでFluxでいうActionは終了。つぎにDispatch。


## AltのDispatcher

Dispatcher自体を自分で書くことはありません。actionで呼ぶくらいです。
ソースを見ればわかりますが、オブションでdispatcherを自分で指定しないかぎり、facebookのシンプルなdispatcherをそのままalt.dispatcherに登録していることがわかります。

```javascript:src/alt/index.js
/*global window*/
import { Dispatcher } from 'flux'

import * as StateFunctions from './utils/StateFunctions'
import * as fn from '../utils/functions'
import * as store from './store'
import * as utils from './utils/AltUtils'
import makeAction from './actions'

class Alt {
  constructor(config = {}) {
    this.config = config
    this.serialize = config.serialize || JSON.stringify
    this.deserialize = config.deserialize || JSON.parse
    this.dispatcher = config.dispatcher || new Dispatcher()
    this.batchingFunction = config.batchingFunction || (callback => callback())
    this.actions = { global: {} }
    this.stores = {}
    this.storeTransforms = config.storeTransforms || []
    this.trapAsync = false
    this._actionsRegistry = {}
    this._initSnapshot = {}
    this._lastSnapshot = {}
  }

  dispatch(action, data, details) {
    this.batchingFunction(() => {
      const id = Math.random().toString(18).substr(2, 16)
      return this.dispatcher.dispatch({
        id,
        action,
        data,
        details
      })
    })
  }

```

参照：  [alt/index.js  goatslacker/alt](https://github.com/goatslacker/alt/blob/8485eee7e592421eefb1596f469eafd43e9cb051/src/alt/index.js)


全てのeventにcallbackを付けることも可能で、そのときは

```
alt.dispatcher.register(console.log.bind(console))
```

や

```
alt.createStore(class MyStore {
  constructor() {
    this.dispatcher.register(console.log.bind(console))
  }
})
```
とするとよいとのこと。

## AltのStore

検索の際にdispatcherが二回叩かれていましたが、どうなるかというと

```javascript:repos-store.js
var alt = require("../alt-application.js")
var ReposActions = require("../actions/repos-actions.js")

class ReposStore {
  constructor() {
    this.repos = []
    this.loading = false
    this.bindAction(ReposActions.search, this.onSearch) #listen
    this.bindAction(ReposActions.searchSuccess, this.onSearchSuccess) #listen
  }
  onSearch() {
    this.loading = true
  }
  onSearchSuccess(repos) {
    this.loading = false
    this.repos = repos
  }
}

module.exports = alt.createStore(ReposStore)

```
上記のようにそれぞれのeventに対してcallbackが宣言されています。なので、searchがはしったときは```this.onSearch```が叩かれ、```this.loading = false``` にまず状態が変わり、すぐにsearchSuccessのcallbackの```this.onSuccessSearch```でloadingがoffになり、APIでとってきたデータがreposに格納されます。


## AltのComponent
最後に再びComponent.

もうここまでくれば簡単で、storeが更新されたわけですから、その変化を監視しているComponentがいます。今回のルートのcomponent(view controllerのようなもの）はreposですから、そこでstoreをlistenしているはずです。こう先が見通せることがFluxの考えのもとで実装を進める利点ですね。


```jsx:repos.jsx
var React = require("react/addons")
var superagent = require("superagent")
var RepoCard = require("./repo-card.jsx")
var ReposStore = require("../stores/repos-store.js")
var ReposActions = require("../actions/repos-actions.js")

module.exports = React.createClass({
  mixins: [React.addons.LinkedStateMixin],
  getInitialState() {
    return ReposStore.getState()
  },
  componentWillMount() {
    ReposStore.listen(this.onChange) #listen
  },
  componentWillUnmount() {
    ReposStore.unlisten(this.onChange)
  },
  onChange() {
    this.setState(ReposStore.getState())
  },
〜〜〜〜〜〜
```

ReposStore.listen(this.onChange)のところで、なにか変化があればcallbackを実行するよう登録されています。
getStateでかえってくるのはstoreのプロパティですので、{loading: false, repos: []}が得られ、stateに登録されます。そしてsetStateされたのでお決まりのviewのレンダリング...と続くところでちょうとfluxのサイクルが一周します。



# 所感

正直まだいろいろ比較しきれてないのでなんともいえない、が結論なんですが、ちょっと見たreduxなどよりはずっと薄く、Fluxの基本概念通りといった感じで読みやすく、環境さえ整えばすぐに取り入れることは出来そうです。
Actionからdispatcher経由でstoreへ、というときにイベントのKEYを管理せずに済むのは楽.他も比較してみるといろいろわかりそうなのであとで追記します。

次は他のライブラリと比較する、railsと組み合わせてどう使うか、ES6バージョンなどを試してみたいと思います。