More than 1 year has passed since last update.

概要

React + Redux のサンプルを使って、Middleware の作成と動作の確認をします。

準備

Reducer 編の続き(tag/reducer)から行います。
※ 実装完了はtag/middleware

メッセージダイアログを表示する Reducer の作成を行います。

Middleware とは

Reducer の実行前後に処理を追加するための仕組みです。

Middleware は次のような形式です。

middleware
store => next => action => {
   :
}

next は次の middleware を表します。 storeaction は名前の通りです。

何もしない middleware は次のようになります。

middleware
store => next => action => {
  next(action);
}

middleware はすべての Action に適用されるため、これを基本形(つまり何もしない)にして、前後に追加の処理をしたり、特定の条件の場合のみ別の処理をすることになります。

next は middleware のチェインなので、次々と middleware が呼ばれ、最終的には dispatch (さらにその先の Reducer) が呼ばれます。

つまり、1つの middleware で見ると、次のようになります。

middleware
store => next => action => {
  // 前の state
  next(action);
  // 後の state
}

例えば、reducer の実行前後のログを取りたい場合、次のようになります。

logger
export const logger = store => next => action => {
  console.log("before: %O", store.getState());
  next(action);
  console.log("after: %O", store.getState());
};

middleware は次のようなことができます。

  • next() を複数回呼ぶ
  • next() を非同期で呼ぶ
  • next() に新しい Action を渡す

この性質を利用して、API 通信を middleware で行い、「通信前に通信中状態の Action」、通信結果に応じて、「通信結果の Action」「通信失敗状態の Action」「リトライ」などの処理を middleware に閉じ込めることができます。

Middleware の作成

上記の logger をサンプルに追加してみます。
middleware/index.js を作成します。

middleware/index.js
export const logger = store => next => action => {
  console.log("before: %O", store.getState());
  next(action);
  console.log("after: %O", store.getState());
};

store/configureStore.dev.js に middleware を追加します。
※ 高機能なロガーの createLogger() はコメントアウトしてください。

store/configureStore.dev.js
import { logger } from "../middleware"

const finalCreateStore = compose(
  applyMiddleware(logger),  // 追加
  applyMiddleware(thunk),
  reduxReactRouter({ routes, createHistory }),
  //applyMiddleware(createLogger()),  // コメントアウト
  DevTools.instrument()
)(createStore);

実行して、Action を実行すると console にログが表示されます。

非同期処理

本当の通信は早すぎるので、タイマーを使ったダミーのサンプルを作成します。

まず、「通信中」「通信結果」の state を作る Reducer と、その Action を作ります。

reducers/index.js
const aServiceInitial = {
  isFetching: false,
  data: ""
};

function aService(state = aServiceInitial, action) {
  const { type, data } = action;

  switch (type) {
    case ActionTypes.SERVICE_GET_DATA:
      return Object.assign({},
        state,
        {
          isFetching: true
        });

    case ActionTypes.SERVICE_SUCCESS:
      return {
        isFetching: false,
        data: data
      };

    default:
      return state;
  }
}
actions/index.js
export const SERVICE_GET_DATA = "SERVICE_GET_DATA";
export const SERVICE_SUCCESS = "SERVICE_SUCCESS";

export function serviceGetData(title, message) {
  return {
    type: SERVICE_GET_DATA,
    api: "http://example.com",
    success: serviceSuccess
  }
}

export function serviceSuccess(data) {
  return {
    type: SERVICE_SUCCESS,
    data: data
  }
}

さらに2つの state を表示する UI と Action を発行するボタンを追加します。

containers/App.js
  handleFetch(e) {
    this.props.serviceGetData();
    e.preventDefault();
  }

  renderFetch() {
    const { aService } = this.props;

    return (
      <div>
        <button type="button" className="btn btn-primary btn-lg" onClick={::this.handleFetch}>
          fetch
        </button>

        <div>data : {aService.data}</div>
        <div>{aService.isFetching ? "fetching..." : ("")}</div>
      </div>
    );
  }

middleware の仕様として Action に action.api があればその url に通信し、action.success に受信したデータを渡すことにします。

middleware/index.js
export const api = store => next => action => {
  next(action);

  if (action.api) {
    setTimeout(() => {
      next(action.success(store.getState().aService.data + store.getState().aService.data.length))
    }, 3000);
  }
};

1つ目の next(action) は何もしない処理で api プロパティを持っていない場合は、ここだけが実行され何も影響を与えません。
SERVICE_GET_DATA の場合も一旦何もせず Reducer を実行させることで isFetchingtrue にし、UI に "fetching..." と表示させています。

次に action.api があるときは、3秒後(通信のシミュレーションです)に next(action ~~) を呼んでいます。
SERVICE_GET_DATA の場合、serviceGetData()action.success にセットされているため、SERVICE_SUCCESS Action が実行されます。

最後に

middleware は Reducer (実際はdispatch) を非同期に実行したり、Action を変えたりできます。
うまく設計することで、Action や Reducer を単純にできます。

参考

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.