reduxのcomposeとapplyMiddlewareとenhancer

  • 29
    いいね
  • 0
    コメント

いつも忘れてしまい、ドキュメントやソースを読んでいる気がするので、それぞれがどういう関数なのかメモ。

compose

http://redux.js.org/docs/api/compose.html

compose(...functions)
Composes functions from right to left.
右から左に関数を合成する

composeは任意の関数を引数に取り、関数を返す。引数の関数はそれぞれ一つの引数を受け取ることが想定されている。

function a(arg) { ...; return something; }
function b(arg) { ...; return something; }

const composed = compose(a, b);

composeで生成されたcomposed関数の動作は下記と同じ。関数の返り値が左隣にある関数の引数に渡され、その関数が返り値は更に左に渡されるという感じ。

function composed(arg) {
  return a(b(arg));
}

applyMiddleware

http://redux.js.org/docs/api/applyMiddleware.html

applyMiddlewareは、redux-promiseredux-thunkなどのMiddlewareを適用するのに使う関数。applyMiddlewareを使うことでdispatch関数をラップし、actionがreducerに到達する前にmiddlewareがキャッチできるようにする。

下記のように使う。

// initialStateを設定する場合
const store = createStore(
  reducer,
  initialState,
  applyMiddleware(middlewareA, middlewareB)
);

// initialStateを設定しない場合
const store = createStore(
  reducer,
  applyMiddleware(middlewareA, middlewareB)
);

// 昔ドキュメントに載っていた書き方(今も有効)
const createStoreWithMiddlewares = applyMiddleware(middlewareA, middlewareB)(createStore);
const store = createStoreWithMiddlewares(reducer, initialState);

middlewareは下記のようなstoreを受け取ってネストした関数を返す関数。一番内側の関数がdispatch時に呼ばれる関数で、その中でactionを変更した場合はstore.dispatchを呼ぶ。特に何もしなかった場合はnext関数を呼ぶと次のmiddlewareの一番内側の関数が呼ばれる。最後のmiddlwareはnext関数が元のdispatch関数になっており、その時点でreducerにactionが渡る。

function middleware(store) {
  return function (next) {
    return function (action) {
      // dispatch時に呼び出される
      if (actionを流さずに別のactiondispatchしたい場合) {
        return store.dispatch(...);
      }
      // nextを実行すると次のmiddleware、またはdispatchが呼ばれる
      next(action);
    };
  };
}

言葉で書くとわかりづらいので、下記のようなmiddlewareAとmiddlewareBがあり、applyMiddlware、createStoreしてstoreを作るとする。わかりやすく、各関数に名前をつけた。

function middlewareA(store) {
  return function a1(nextA) {
    return function a2(action) {
      nextA(action)
    };
  };
}

function middlewareB(store) {
  return function b1(nextB) {
    return function b2(action) {
      nextB(action);
    };
  };
}

const store = createStore(
  reducer,
  applyMiddleware(middlewareA, middlewareB)
);

applyMiddleware実行後のdispatchは下記のようなイメージ。nextAにはb2nextBにはstore.dispatchが渡ってくる。

store.dispatch = a1(b1(store.dispatch));

// 実際はcomposeを使っている
store.dispatch = compose(a1, b1)(store.dispatch);

一番内側の関数(a2, b2)で表現するとこんなイメージで、middlewareAで処理したactionがmiddlewareBに渡り、middlewareBで処理したactionが元のdispatch関数に渡る。

// ※イメージです。実際は同期的じゃないです。
// middlewareA -> middlewareB -> 元のdispatchとactionが渡っていくということが言いたい
const originalDispatch = store.dispatch;
store.dispatch = function (action) {
  return origilanDispatch(b2(a2(action)));
};

なので、applyMiddlewareの引数の順序を正しく並べないと意図していない挙動を招く可能性がある、かも。(そんなmiddlewareを作っている人が悪いかもしれないけど)

enhancer

http://redux.js.org/docs/api/createStore.html

createStore(reducer, [preloadedState], [enhancer])

createStoreの第3引数(または第2引数)は、applyMiddleware専用というわけではなく、enhancerという関数が渡ってくることを想定している。

enhancer: The store enhancer.

enhancerはcreateStoreを引数に受け取って、ラップしたcreateStoreを返す関数。applyMiddlewareの場合、ラップしたdispatch関数を持つstoreを生成するためのcreateStoreを返す。

function enhancer(createStore) {
  // ラップしたcreateStore
  return function wrappedCreateStore(reducer, preloadedState) {
    // storeを生成して返す
    return store;
  }
}

おわり

  • これでしばらく忘れなそう。
  • applyMiddleware以外のenhancerをあまり見たことがない(Redux DevToolくらいしか知らない)ので、他のパターンも見てみたい。もしくは、何か作ってみるか。