Redux Middleware in Depth


Middleware使ってますか？ 以前 [Reduxのmiddlewareを積極的に使っていく](http://qiita.com/kuy/items/57c6007f3b8a9b267a8e) という記事でMiddlewareの使いどころについて具体的な利用例を挙げました。本稿ではMiddlewareを書く上で役に立つ、もうちょっと掘り下げたTipsを紹介していきます。

## ActionがMiddlewareを通り抜ける順番

```middlewares.js
export const m1 = store => next => action => {
  console.log('m1', action.type);
  return next(action);
};

export const m2 = store => next => action => {
  console.log('m2', action.type);
  return next(action);
};

export const m3 = store => next => action => {
  console.log('m3', action.type);
  return next(action);
};
```

```store.js
import { createStore, applyMiddleware } from 'redux';
import reducer from './reducers';
import { m1, m2, m3 } from './middlewares';

export default createStore(
  reducer,
  applyMiddleware(m1, m2, m3)
);
```

抜粋ですが上記の構成で何か適当なAction、ここでは `HOGE` Actionをdispatchすると以下の出力を得ます。

```
m1 HOGE
m2 HOGE
m3 HOGE
```

素直に `applyMiddleware` で指定した順番にActionが通り抜けています。
強いて言えばMiddlewareによってはどこに置くかが大事なものがあるのでそこは注意すべきです。

例えば [`redux-logger`](https://github.com/fcomb/redux-logger) は [`README.md`](https://github.com/fcomb/redux-logger#usage) にも書いてありますが **原則的に一番最後** に置きます。

```store.js
import logger from 'redux-logger';

export default createStore(
  reducer,
  applyMiddleware(your, awesome, middleware, logger())
);
```

`redux-logger` 自体はただのロガーなのでどこに置いても動作しますが、最後に置くのはログ出力されるActionと実際にStoreに送り込まれる(その結果としてStateを変化させる)Actionに食い違いが出て混乱するからです。以下のようにわざと途中に `redux-logger` を置いたMiddleware構成を想定してみます。

```
applyMiddleware(m1, logger(), m2, m3)
```

ここで `m2` が `FOO` Actionを潰して、`m3` が別の `BAR` Actionを生成する場合、`FOO` Actionと `HELLO` Actionを送り込むと `redux-logger` のログ出力には `FOO` Actionと `HELLO` Actionが表示されますが、実際にStoreに到達するActionは `HELLO` Actionと `BAR` Actionになります。

```
[M1] -> [Logger] -> [M2] -> [M3] -> [Store]

[FOO] --------------> X
[HELLO] ------------------------------->
                            [BAR] ----->
```

同様にMiddlewareを置く位置が大事なものとして [`redux-thunk`](https://github.com/gaearon/redux-thunk) があります。`redux-thunk` はActionオブジェクトの代わりに関数オブジェクトをdispatchすると、Storeの `dispatch` 関数と状態取得のための `getState` 関数を渡されるのであとは煮るなり焼くなりしてくれ、という良く言えば **強力な**、悪く言えば **GOTO文のような** Middlewareです。こちらは `redux-logger` と逆で **一番最初に置く** 必要があります。

```store.js
import thunk from 'redux-thunk';

export default createStore(
  reducer,
  applyMiddleware(thunk, your, awesome, middleware)
);
```

理由は単純で、多くのMiddlewareはActionとして関数オブジェクトがやって来ることは想定していないので、すべてのMiddlewareに先んじて関数を実行してActionオブジェクトを得てから後続のMiddlewareに流してあげる必要があるためです。

```
        [Thunk] -> [M1] -> [M2] -> [Store]

[Func] --> X ----> [A1] -------------->
```

[`redux-api-middleware`](https://github.com/agraboso/redux-api-middleware) とかも同じで最初に置く必要があります。 

### では `redux-thunk` と `redux-api-middleware` はどっちを先にするべきでしょうか？

答えは **基本的にはどっちでもいい** です。`redux-thunk` は `redux-api-middlware` の `CALL_API` Actionを無視してくれますし、`redux-api-middlware` は `redux-thunk` で処理される関数オブエジェクトを無視するからです。

`redux-api-middleware` の [`types`](https://github.com/agraboso/redux-api-middleware/blob/master/README.md#call_apitypes) に関数オブジェクトを渡して `redux-thunk` に実行させるような ~~変態的~~ 特殊なことをやりたくても、現状では `redux-api-middleware` のバリデーションによってはじかれるのでできません。残念。もし将来 `types` に関数オブジェクトを渡せるようになったら **`redux-api-middleware` を先に置く** 必要があります。逆にすると関数オブジェクトは実行されずに後続のMiddlewareまたはStoreでエラーが発生します。これは `redux-api-middleware` がAPI呼び出しの状況や結果を伝えるActionを投げるときに **`store.dispatch` を使わずに `next` を使っている** ことに起因します。次のセクションではこの違いが何を意味しているのか考えていきます。

## MiddlewareからActionを投げる方法は2つある

Middlewareはだいたい以下のような形式で書きます。

```hoge.js
export const hoge = store => next => action => {
  // ...
};
```

Middlewareによっては一番外側の関数をarrow functionではなく普通に関数として定義してたり、引数を受け取るとき `(store)` の代わりに `({ getState })` と書いてStoreに対しては状態の取得しかしないよ！と明示するものもありますね。

さて、Middlewareでは新しくActionオブジェクトを生成してStoreにdispatchすることができますが、その方法は2つあることはご存知でしょうか？ 例えば `FOO` Actionが来たら `BAR` Actionを新しく作って投げるMiddlewareを考えてみます。

```m1.js
export const m1 = store => next => action => {
  if (action.type === 'FOO') {
    next({ type: 'BAR' });
  }
  return next(action);
};
```

特別なことはありません。それではもう1つの方法を以下に示します。

```m2.js
export const m2 = store => next => action => {
  if (action.type === 'FOO') {
    store.dispatch({ type: 'BAR' });
  }
  return next(action);
};
```

このようにStoreの `dispatch` 関数を直接呼び出すこともできます。言われてみるとなんてことはないんですけどね・・・。

### では `next` と `store.dispatch` の違いは異なる結果を生み出すのでしょうか？

Middlewareが1つだけのときに限って **NO** ですが、 **基本的にはYES** なので注意が必要です。具体的にどういう違いがあるのか詳しく見ていきます。次のコードは冒頭の3つのMiddlewareのサンプルをベースに、真ん中の `m2` の代わりに `next` を使う `m2a`、`store.dispatch` を使う `m2b` に書き換えました。動作としてはどちらも `FOO` Actionが来たら `BAR` Actionを生成して投げます。

```middlewares.js
export const m1 = store => next => action => {
  console.log('m1', action.type);
  return next(action);
};

export const m2a = store => next => action => {
  console.log('m2a', action.type);
  if (action.type === 'FOO') {
    next({ type: 'BAR' });
  }
  return next(action);
};

export const m2b = store => next => action => {
  console.log('m2b', action.type);
  if (action.type === 'FOO') {
    store.dispatch({ type: 'BAR' });
  }
  return next(action);
};

export const m3 = store => next => action => {
  console.log('m3', action.type);
  return next(action);
};
```

上記Middlewareを使ってまずは `m2a` を使った場合、`applyMiddleware(m1, m2a, m3)` の出力を見てみます。

```
m1 FOO
m2 FOO
m3 BAR
m3 FOO
```

次に `m2b` を使った場合、`applyMiddleware(m1, m2b, m3)` の出力です。

```
m1 FOO
m2 FOO
m1 BAR
m2 BAR
m3 BAR
m3 FOO
```

これで違いは明らかですね。 **`next`は後続のMiddlewareのみ通過するのに対し、`store.dispatch`はMiddlewareチェーンの最初から実行されます。** どちらを使うべきかは何をしたいのかに依存します。例えば `redux-thunk` では `store.dispatch` を使うことを強制されますが、もし `next` を使えてしまうと `redux-thunk` をMiddlewareチェーンのどこに置いたかによって適用されるMiddlewareが変わってしまい混乱の元になります。さらに `store.dispatch` を使うことによって関数オブジェクトがさらに関数オブジェクトをdispatchするような状況でも再度 `redux-thunk` が実行してくれます。このように `redux-thunk` では `store.dispatch` を使わなければならない強い理由があります。

一方で新しいActionを投げるのに絶対に `next` を使わなければならない状況というのはそんなに多くありません。 **だからといって安易に`store.dispatch`を使うのは危険です。**注意して作らないとMiddlewareがActionをdispatchすることで再帰的にMiddleware自身が呼び出されてしまう **無限ループ** に陥ります。

例えば上記のサンプルでは `FOO` Actionのときのみ `BAR` Actionをdispatchしていますが、その条件をはずすと当然無限ループになります。`redux-thunk` も関数オブジェクトであれば問答無用で実行するので、次のような意地悪なActionを投げると `Uncaught RangeError: Maximum call stack size exceeded` エラーが発生します。

```
const recurring = dispatch => dispatch(recurring);
this.props.dispatch(recurring);
```

## MiddlewareからActionを投げるタイミングは2つある

Middleware内からActionを投げるのに2つの方法があることを知りましたが、まだ安心できません。 **どのタイミングで投げるのか** によって結果が変わってきます。以下のMiddlewareがどのような挙動になるのか想像してみてください。

```
function isTarget(action) {
  return action.type === 'FOO';
}

export const m1 = store => next => action => {
  if (isTarget(action)) {
    store.dispatch({ type: 'BAR1' });
  }
  const ret = next(action);
  if (isTarget(action)) {
    store.dispatch({ type: 'BAR2' });
  }
  return ret;
};
```

オリジナルのActionを `next` で送り出す **前に** 新しいActionをdispatchするのか、 **後に** dispatchするのかで変わってきます。これをうまく利用しているのが `redux-logger` です。`redux-logger` はStoreにdispatchされたActionを表示するだけでなく、dispatch前後のStateも出力してくれます。Storeの `getState` 関数を `next` の前後でそれぞれ呼び出すことで実現しています。

## まとめ

3行でまとめると、

+ `applyMiddleware` で指定するMiddlewareの位置に気をつける
+ Actionを投げる方法は `next` と `store.dispatch` の2つがある
+ オリジナルのActionを `next` で呼び出す前後でStateが変化する

## おまけ

複雑なMiddlewareチェーンを組んでいるとどこでActionが生成されたのか、改変されたのか、潰されたのかわからなくなることがあります(怖い)。そんなとき手っ取り早くprintデバッグする方法は `redux-logger` をひたすらMiddlewareの間に差し込みます。

```store.js
import logger from 'redux-logger';

export default createStore(
  reducer,
  applyMiddleware(logger(), your, logger(), awesome, logger(), middleware, logger())
);
```

うわぁひどい・・・。けど、 ~~惨状~~ 状況は一応つかめます。ただこれもMiddlewareの数が多くなると挿入するのが面倒になるので、お手軽にやるには [redux-middleware-logger](https://github.com/kuy/redux-middleware-logger) が便利です。使い方は簡単でMiddlewareのリストを渡します。

```store.js
import withLogger from 'redux-middleware-logger';

export default createStore(
  reducer,
  applyMiddleware(...withLogger(your, awesome, middleware))
);
```

![Screen Shot 0028-03-21 at 02.51.26.png](https://qiita-image-store.s3.amazonaws.com/0/69860/6400963e-60a6-8969-644d-c767862b9f34.png)


あまりインストールされていませんね。みなさんそんなに困っていないようで安心しました。