146
144

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Redux 基礎:Middleware 編

Posted at

概要

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 を単純にできます。

参考

146
144
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
146
144

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?