yahoo/fluxible関連

https://github.com/yahoo/fluxible

http://fluxible.io/


まだ自分用のメモ書きレベルです…

公式サイトに乗っているQuickStartで作ったプロジェクトを前提に書きます。まだの人は、次のコマンドを打ってください。Windowsの方は後述のTipsに有るスクリプトを定義しないと動かないです。

```shell-session
npm i -g yo generator-fluxible
yo fluxible
npm run dev
```

なお、yoのテンプレートの依存パッケージが古くてうまく動かないことがあるので、最低babelくらいはアップデートしておきましょう。


# ディレクトリ構成

- actions
    - アクションの定義 (yo fluxibleでは入らないものの作ると便利)
- components
    - Viewの定義
- stores
    - ストアの定義
- build
    - webpackの出力ディレクトリ(IDEなど使う場合は除外する)
- configs
    - ルーティングなどの定義


# 使用されている(学ぶべき)技術

- Babel (ECMA Script6)
- ReactJS
- Webpack
- ES Lint


# Tips

## windowsで'npm run dev'を動かす

標準が`&`使ったワンライナーなので、Linux系でしか動かない。
package.jsonに以下のスクリプトを定義して、`npm run dev-win`を叩く。
Grup使ってもOK!

```json
{
  "scripts": {
    "dev-win": "start node webpack-dev-server.js & SET PORT=3001 & nodemon start.js -e js,jsx"
  }
}
```

## ウザいブラウザのログの消し方

ブラウザのコンソールから

```javascript
localStorage.debug = "" // OFF
localStorage.debug = "*" // ON
```

`npm debug`を使用しているので、その他のオプションはここから探せる気がする。
https://www.npmjs.com/package/debug

前に書いた記事: http://qiita.com/kamijin_fanta/items/55795bd3936cc3a7fc5a


## クライアントだけで描写したい

server.jsから編集。

- `executeAction(navigateAction…`とかは見ての通り、URLでのナビゲーションを行っているだけなのでサーバ側には不要。
- stateをdehydrateして取得する必要も渡す必要もない無い。
- markupもコンポーネントをブラウザに書かせればいいので不要。

```javascript
// server.js
server.use((req, res, next) => {
    let context = app.createContext();

    debug('Rendering Application component into html');
    const html = React.renderToStaticMarkup(htmlComponent({
        clientFile: env === 'production' ? 'main.min.js' : 'main.js',
        context: context.getComponentContext(),
        // state: exposed,
        // markup: React.renderToString(createElementWithContext(context))
    }));

    debug('Sending markup');
    res.type('html');
    res.write('<!DOCTYPE html>' + html);
    res.end();
});
```

server.jsでやっていたnavigateActionをブラウザでやるだけ。urlには`location.pathname + location.search`を指定することによってルーティングできる。

```javascript
// client.js

import {navigateAction} from 'fluxible-router';
let context = app.createContext();
context.getActionContext().executeAction(navigateAction, {
    url: location.pathname + location.search
}, (err) => {
    if (err) {
        if (err.statusCode && err.statusCode === 404) {
            next();
        } else {
            next(err);
        }
        return;
    }

    window.context = context;
    const mountNode = document.getElementById('app');

    debugClient('React Rendering');
    React.render(
        createElementWithContext(context),
        mountNode,
        () => debugClient('React Rendered')
    );
});
```


## RouteとNavLink

### あってもなくても良いURLパラメータの指定

```js
// routes.js
home: {
    path: '/:id?/',
},
```

```html
<NavLink routeName="home" navParams={{id: null}}>Home</NavLink>
<NavLink routeName="home" navParams={{id: 123}}>Home 123</NavLink>
```

最後の/が無いとパラメータ無指定時に落ちる。ちなみに、`routes.js`を編集した時はサーバごと再起動させたほうが良い。

パスのコンパイルの詳しい挙動は https://github.com/pillarjs/path-to-regexp/ のテストを見たら分かった。


## URLパラメータを取得する

ルーティングの設定の任意のpathにパラメータの名前を指定する。

```js
// routes.js
path: '/member/:user?/'
```

`Component`の`connectToStore`に追記する。重要箇所に☆マークつけた。

```js
// components/ComponentName.js
export default connectToStores(
    ComponentName,
    [ApplicationStore, "RouteStore"], // ☆
    function (context, props) {
        var appStore = context.getStore(ApplicationStore);
        var routeStore = context.getStore("RouteStore"); // ☆
        var params = routeStore.getCurrentRoute().get("params").toObject(); // ☆

        return {
            params: params, // ☆
            user: params.user
        };
    }
);
```

コンポーネントから`this.props.params`で取得できるようになる。


## クエリを含んだURLをNavLinkで生成する

結論から言うと、用意されていない。

### Case1 makePath

コンポーネント上で`this.props.context.makePath(route, parm)`を使用することによってURLを取得できる。それに`+"?key=value"`のような感じでクエリ付きのURLを生成し、JSXにより`a`タグを生成する。

### Case2 Extend NavLink

`fluxible-router`の`createNavLinkComponent`は、引数にオブジェクトを渡して呼び出すと拡張できる。1行目でクエリ文字を生成し、最後の行で結合させる。

```js
// library/QueryNavLink.js
import { createNavLinkComponent } from 'fluxible-router';
import querystring from 'querystring';

export default createNavLinkComponent({
    _getHrefFromProps: function (props) {
        var query = props.query==undefined?"":"?" + querystring.stringify(props.query);

        var href = props.href;
        var routeName = props.routeName;
        var routeStore = this.context.getStore("RouteStore");
        if (!href && routeName) {
            href = routeStore.makePath(routeName, props.navParams);
        }
        if (!href) {
            throw new Error('NavLink created without href or unresolvable routeName \'' + routeName + '\'');
        }
        return href + query;
    }
});
```

あとは普通に使うときにqueryパラメータをつけるだけ。

```js
// components/ComponentName.js
import NavLink from '../library/QueryNavLink';
<NavLink routeName="mail" query={{page: 1}}>テキスト</NavLink>
```

アクセスは、Route Dataのqueryを見るだけ。一応公式ドキュメントにも記述の通り、複数の値が見つかった場合は、配列として格納される点を注意。

```js
export default connectToStores(
    Foo,
    ["RouteStore"],
    function (context, props) {
        var routeStore = context.getStore("RouteStore");
        var query = routeStore.getCurrentRoute().get("query").toObject()

        return {
            query: query
        };
    }
);

```

https://github.com/yahoo/fluxible-router/blob/master/docs/api/navigateAction.md


# トラブルシューティング

すこし「ん？」って思ったところを挙げます。

## this.context.executeAction is not a function

使用すると明示的に指定したComponentでしかexecuteActionは使えない。

```javascript
// components/ComponentName.js
ComponentName.contextTypes = {
    executeAction: React.PropTypes.func.isRequired
}
```

## Store undefined was not registered.

app.jsでStoreを登録しないと使えない。

```javascript
// app.js
app.registerStore(TestStore);
```


## Cannot read property 'storeName' of undefined

`app.js`のRouteStoreの読み込み位置を変える。Webpackの都合なのか、数回ハマった。

```js
// app.js
import RouteStore from './stores/RouteStore'; // Application,ApplicationStoreより上に持っていくる
import Application from './components/Application';
import ApplicationStore from './stores/ApplicationStore';
```

第二引数のストアは、名前でも渡せるのでそっちのほうが良いかも。文字列で渡すと表題のエラーは起こらない。特に`RouteStore`関係でエラーが出るのでその辺は注意が必要。

```javascript
export default connectToStores(
    Application,
    ["ApplicationStore"],
    function (context, props) {
        // code
    }
);
```