Nanox - react-micro-containerをベースにしたお手頃規模のFlux系フレームワーク

## はじめに

素のReactだと面倒。でもFluxやReduxを使う程でもない規模のプロジェクトで何を使うか、というテーマに対する最適解を紹介します（胡散臭げな顔で）。

小規模なReact拡張ライブラリとして、例えば[react-micro-container](http://qiita.com/hokaccha/items/76332e9863c067522835)や[flumpt](http://qiita.com/mizchi/items/6a3500e598ec36746509)などがあります。

これらはフレームワークというよりはReactアプリケーションの実装を手助けする補助的役割がメインで、素のReactを__徒歩__とすると、react-micro-containerやflumptは__自転車__的な位置付けになるかと思います。

それに対し、FluxやReduxなどの本格的なフレームワークは__2tトラック__とかそんなところでしょうか。

徒歩は論外、自転車でもちょっとしんどい、かといって2tトラックは大袈裟なんだよなぁ・・・といった時の選択肢として今回紹介するのが拙作の[Nanox](https://www.npmjs.com/package/nanox)です。

規模としては__バイク__とか__軽自動車__とか、そのくらいを想像していただければ（分かりやすいようで分かりにくい謎の例え）。

図にするとこんな感じです。

![flow.png](https://qiita-image-store.s3.amazonaws.com/0/17021/1e5fb6ca-163f-6e97-7b84-851337c0b4d1.png)

## 使用方法

Nanoxはreact-micro-containerをベースにしたフレームワークなので、[こちらの記事](http://qiita.com/hokaccha/items/76332e9863c067522835#usage)をベースにどんなものか説明していきます。

基本部分は大体同じです。

### 子コンポーネント

> #### react-micro-containerの記事から抜粋
>
> まず普通のステートレスなReactコンポーネントを作る。`dispatch`という`props`を受け取ってそれを通してイベントを発火するというのが唯一の規約。
>
> ```js:components/counter.js
> import React from 'react';
>
> // Stateless component
> export default class Counter extends React.Component {
>   render() {
>     return (
>       <div>
>         <div>{this.props.count}</div>
>         <button onClick={() => this.props.dispatch('increment', 1)}>+1</button>
>         <button onClick={() => this.props.dispatch('decrement', 1)}>-1</button>
>         <button onClick={() => this.props.dispatch('increment', 100)}>+100</button>
>       </div>
>     );
>   }
> }
> ```

ここはNanoxでも全く同じです。

### コンテナコンポーネント

> #### react-micro-containerの記事から抜粋
>
> それをwrapするコンテナコンポーネントを作る。こいつはstateを持ち、子のコンポーネントに`dispatch`を渡してイベントをsubscribeし、適時stateを更新する。
>
> ```js:container/counter.js
> import MicroContainer from 'react-micro-container';
> import Counter from '../components/counter';
>
> export default class CounterContainer extends MicroContainer {
>   constructor(props) {
>     super(props);
>     this.state = { count: 0 };
>   }
>
>   componentDidMount() {
>     this.subscribe({
>       increment: this.handleIncrement,
>       decrement: this.handleDecrement,
>     });
>   }
>
>   handleIncrement(count) {
>     this.setState({ count: this.state.count + count });
>   }
>
>   handleDecrement(count) {
>     this.setState({ count: this.state.count - count });
>   }
>
>   render() {
>     return <Counter dispatch={this.dispatch} {...this.state} />;
>   }
> }
> ```

ここからNanox独自の実装が入ってきますので、拡張ポイントを説明していきます。

上記のreact-micro-containerでの実装部分をNanoxでは以下のように書きます。

```js
import Nanox from 'nanox'; // 変更点①
import Counter from '../components/counter';
import actions from './actions'; // 変更点②

export default class CounterContainer extends Nanox { // 変更点③
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }

  componentDidMount() {
    this.registerActions(actions); // 変更点④
  }

  // 変更点⑤

  render() {
    return <Counter dispatch={this.dispatch} {...this.state} />;
  }
}
```

#### 変更点①

`react-micro-container`をインポートするかわりに`nanox`をインポートします。

#### 変更点②

`actions.js`という外部ファイルをインポートしています。react-micro-container版ではコンテナコンポーネント内に直接記述していた`handleIncrement`と`handleDecrement`をこちらの`actions.js`に切り出しています。

#### 変更点③

変更点①に合わせて、`MicroContainer`を継承していた部分を`Nanox`を継承するように変更しています。

※`Nanox`は`MicroContainer`を継承していくつかの機能拡張を付加したクラスです。

#### 変更点④

react-micro-container版では`this.subscribe()`でイベント登録を行っているところを、Nanox版では`this.registerActions()`というメソッドでイベント登録を行っています。

見た感じは`this.subscribe()`と同じように見えますが、`this.registerActions()`では内部で色々といい感じにやってくれた後に`this.subscribe()`でイベント登録をしています。

具体的に何がいい感じなのかについては後述します。

#### 変更点⑤

変更点②に合わせて、react-micro-container版で記述していた`handleIncrement`と`handleDecrement`がなくなっています。


### マウント

> #### react-micro-containerの記事から抜粋
>
> このコンポーネントも単なるReact Componentなのでこのコンテナを普通にマウントするだけ。
>
> ```js:app.js
> import ReactDOM from 'react-dom';
> import CounterContainer from './container/counter';
>
> ReactDOM.render(<CounterContainer />, document.getElementById('app'));
> ```

こちらもreact-micro-container版とNanox版で違いはありません。

### まとめ

以上のように、Nanoxでreact-micro-containerから拡張したのはコンテナコンポーネントの部分だけという事になります。

それ以外はreact-micro-containerと同じ使用感で書くことができます。

## Nanoxにおけるアクション登録方法

上記使用方法で紹介したコンテナコンポーネントの説明でさらっと流していた部分について掘り下げていきます。

### actions.jsの内容

上記変更点②で`actions.js`という外部ファイルにイベント処理を切り出しました。ここではその`actions.js`の内容について説明します。

以下はreact-micro-container版での`handleIncrement`と`handleDecrement`を同期、非同期で実装したイベントのサンプルです。

なお、以降はアクションという名称で呼んでいきます。

```js:actions.js
const actions = {
  increment(count) {  // ポイント①
    const currentState = this.getState();  // ポイント②
    return {  // ポイント③
      count: currentState.count + count
    };
  },

  decrement(count) {
    return new Promise((resolve, reject) => {  // ポイント④
      setTimeout(() => {
        const currentState = this.getState();
        resolve({  // ポイント⑤
          count: currentState.count - count
        });
      }, 1000);
    });
  }
};

// ポイント⑥
export default actions;
```

#### ポイント①

`increment`というのがアクション名です。子コンポーネントから実行される`this.dispatch()`で指定されるやつです。

```js
                                            コレ
                                             ↓
<button onClick={() => this.props.dispatch('increment', 1)}>+1</button>
```

アクション名の後に指定されている引数の`count`は`this.dispatch()`でアクション名の後に指定されたものです。複数指定も可能です。

```js
                                                        複数指定した場合
                                                           ↓
<button onClick={() => this.props.dispatch('increment', 1, 2, 3)}>+1</button>
```

複数指定した場合のアクションでの受け取り方は以下のようになります。

```js
increment(x, y, z) {
  // x = 1, y = 2, z = 3
```

#### ポイント②

アクション内では`this.getState()`というメソッドによって現在のコンテナコンポーネントの最新の`state`を取得できます。

なお、ここで取得した`state`はコンテナコンポーネントの`state`のコピーなので、この値を直接変更してもコンテナコンポーネントの`state`は更新されません。

#### ポイント③

このアクション内で更新したい部分だけをオブジェクトで返すことにより、コンテナコンポーネントの`state`が更新されます。

#### ポイント④

`Promise`を返すことにより非同期アクションの実装も可能です。

#### ポイント⑤

`Promise`内の処理で、ポイント③と同様に更新したい部分のオブジェクトを`resolve`することによりコンテナコンポーネントの`state`を更新することができます。

#### ポイント⑥

アクションをエクスポートしてコンテナコンポーネントからインポートできるようにします。

### まとめ

ぶっちゃけ、別ファイルに切り出さずにコンテナコンポーネント内の`this.registerActions()`に直接アクションをぶっこんでしまってもよいのですが、コンポーネント部分は極力描画だけに専念してもらおうという計らいです。

## `this.registerActions()`のいい感じなところ

### 1. シンプルなアクションハンドラ

react-micro-containerの`this.subscribe()`で登録するイベントはあくまでもただのイベントなので、各イベントハンドラが`this.setState()`でコンテナコンポーネントの`state`を更新する所まで面倒を見る必要があります。

Nanoxの`this.registerActions()`で登録したアクションは更新する部分のみを返すだけで良いというのと、非同期処理も`Promise`で包むだけで良いので、毎回同じようなお作法的処理を書く必要がなく、処理がスッキリします。

### 2. アクション名のTypoを検知

react-micro-containerの`this.subscribe()`で登録するイベントはあくまでもただのイベントなので（2回目）、`this.dispatch()`に指定するイベント名を間違えたとしても何も起きないだけで、イベント名を間違えたかどうかというところまでは分かりません。

Nanoxでは`this.registerActions()`で登録したアクション以外のアクション名が`this.dispatch()`で指定された時にエラーで教えてくれます。

### 3. 自動エラーハンドリング

Nanoxの`this.registerActions()`時にこっそりとエラーハンドリング用の隠しアクション（`__error`）が登録されます。

ユーザーが登録したアクション内で例外が発生したり、`Promise`が`reject`された時には自動的にこの`__error`アクションが`dispatch`されます。

各アクション内で個々に`try`～`catch`しなくても、まとめて`__error`がエラーの面倒を見てくれます。逆に面倒を見てほしくない場合は自前で`try`〜`catch`して例外を捕捉すればOKです。

### 4. エラーハンドラのカスタマイズ

Nanoxデフォルトの`__error`アクションはエラー内容を`console.error`で表示するだけですが、ユーザー側で登録するアクションに`__error`という名称のアクションを実装することによってこの挙動を[上書き](https://github.com/ktty1220/nanox#custom-error-handler)することができます。

## まだ質問なんてないけどFAQ

__Q.__ アクション内の処理開始時と終了時に`setState()`したいんだけど
__A.__ 例えばAjaxでサーバーからデータを取得する処理の開始時に「通信中」的な表示をするためにコンテナコンポーネントの`state`に通信中フラグをたてて、通信が終了した時にそのフラグを解除するみたいなやつのことです。

確かに、更新部分を返すことによってコンテナコンポーネントの`state`を更新する形だとこういった2段階にわたっての更新はできないように思えます。

が、アクション内では`this.dispatch()`によって別のアクションを呼び出すことができるので、上記のようなケースはこのような感じで実装することができます。

```js
const actions = {
    .
    .
    .

  getData(url) {
    // getDataアクション内でwaitingアクションを呼び出し
    this.dispatch('waiting', true);

    return new Promise((resolve, reject) => {
      fetch(url)
      .then((response) => response.json())
      .then((json) => {
        resolve({
          data: json,
          waiting: false // 通信終了時にコンテナコンポーネントのwaitingを解除
        });
      });
    });
  },

  waiting(flag) {
    // waitingアクションでコンテナコンポーネントのstateを更新
    return {
      waiting: flag
    };
  }

    .
    .
    .
```

なお、アクション内の`this`は、この`dispatch()`と先ほど紹介した`getState()`のみを実装しています。各アクションのあちこちで`setState()`や`subscribe()`を呼び出したり、`state`を直接更新するような世紀末カオス状態に陥るのを防止するための制限となります。

## デモ

Nanoxのソースリポジトリに同梱されている[サンプル](https://codesandbox.io/s/pwz0prn3yq)のデモになります。

## さいごに

大勢のスタッフが関わる大規模アプリケーションならFlux、Reduxのようなガッチリとしたフレームワークがベストチョイスになると思いますが、個人や数人規模で管理していくものであればこれくらいこじんまりしてた方が見通しが良いのではないでしょうか。

まあ、同じようなコンセプト（バイクや軽自動車規模）のFlux系フレームワークはいくらでもあるとは思いますが、その中の選択肢の1つとして、こじんまりとはしつつも無茶はさせない制約と多少の気の利いたエラー検知機能を備えたNanoxを検討してみてはいかがでしょうか、なんて。

おっと忘れてたインストールは、

```sh
$ npm install nanox
```

で、ソースは[ここ](https://github.com/ktty1220/nanox)にありますのでPRなどありましたらよろしくお願いします。