SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

基礎からはじめるReact入門

Reduxで非同期処理を行う~Middlewareを使って実装する方法

基礎からはじめるReact入門 第9回


redux-thunkで非同期処理を実現する

 redux-thunkはReduxにMiddlewareとして組み込むために開発されているライブラリです。通常、dispatch関数にはActionのオブジェクトしか渡すことができませんが、このMiddlewareを適用したRedux環境では、dispatch関数に関数オブジェクトを渡すことができるようになります。これによって何がうれしいのかというと、Action Creatorの作り方が変わるのです。リスト7に例を挙げます。

[リスト7]redux-thunk使用前後のAction Creator
// redux-thunk使用前のAction Creator
function doSomething() {
  return { type: "SOMETHING" }
}

// redux-thunk使用後のAction Creator
function doSomething() {
  return (dispatch) => { // (1)
	fetch("https://example.com/api/hoge")
	.then(response => {
	  // 非同期処理の後にdispatchを呼べる
	  dispatch({ type: "SOMETHING", payload: response.ok }); // (2)
	})
  }
}

 redux-thunk使用後、Action Creatorが返却する関数オブジェクトには、dispatch関数が渡されます(1)。このdispatch関数を使うことで、関数オブジェクトの内部で実施した非同期処理の結果を用いたdispatch呼び出しが可能となります(2)。Reactからは通常のAction Creatorと同じようにdispatch(doSomething())といった呼び出し方をしていますし、Reducer側から見ても従来通りの方法でActionが発行されたように見えるはずです。

 このように、dispatchした後、かつReducerが処理する前のタイミングで、非同期処理を許容しながらActionを加工・生成していくのが、Middlewareという仕組みの真骨頂です。Middlewareをちゃんと理解しようとすると大変ですが、「そういった特性を持ったものだ」とだけ理解しておけば、他のMiddlewareを採用する際にも役割を理解しやすいのではないかと思います。

redux-thunkを導入する

 それでは、redux-thunkをアプリケーションに組み込んでいきましょう。reduxreact-reduxは導入済みであるものとします。まずは、リスト8のようにredux-thunkをインストールしてください。

[リスト8]redux-thunkをインストールする
$ npm install redux-thunk --save

 次に、前回も使用したReduxのcreateStore関数を使って、redux-thunkを組み込みます(リスト9)。

[リスト9]redux-thunkをReduxに適用する
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

const store = createStore(
  rootReducer,
  applyMiddleware(thunk) // (1)
);

 ポイントは、createStore呼び出しの第二引数に、applyMiddleware(thunk)でredux-thunkのモジュールを渡しているところです(1)。他のMiddlewareを導入するときには、applyMiddleware関数に渡す引数を増やすことで対応できます。

 さて、これでredux-thunkを使う準備は完了です。もうdispatch関数に関数オブジェクトを渡せるようになりました。

非同期処理を実装する

 それではredux-thunkを使った非同期処理を実装していきましょう。リスト6で使っていたinitPostssendNewPostを実装していきます(リスト10)。今回は実際にはサーバーと通信しているわけではありませんが、それらしく見えるよう、データ取得用のApiClient.fetchPosts()とデータ送信用のApiClient.sendPost(newPost)を用意しました。それらしく動くようにもしてあるので、興味がある方はサンプルコードを御覧ください。

[リスト10]src/actions/posts.js
/** サーバーのデータを取得して内部状態を初期化する */
export function initPosts() {
  return (dispatch) => {
    // 最新の投稿データすべてをサーバーから取得する
    ApiClient.fetchPosts() // (1)
      // 成功したらReducerに投稿データを渡す
      .then(posts => dispatch(updatePosts(posts))) // (2)
      // 失敗したらReducerに失敗したことを通知する
      .catch(e => dispatch(initFailed(e))) // (3)
  }
}
/** サーバーに新しい投稿を送信する */
export function sendNewPost(newPost) {
  return (dispatch) => {
    // 投稿データをサーバーに送信する
    ApiClient.sendPost(newPost) // (1)
      // 送信に成功したら送信後の投稿データすべてをサーバーから取得する
      .then(() => ApiClient.fetchPosts()) // (1)
      // データの取得に成功したらReducerに投稿データを渡す
      .then(posts => dispatch(updatePosts(posts))) // (2)
      // 失敗したらReducerに失敗したことを通知する
      .catch(e => dispatch(sendingFailed(e))) // (3)
  };
}

 さて、初期化も送信も、基本的な作りは同じです。非同期処理を実行する(1)、成功していれば成功した事実やそれによって得られたデータを別のAction Creatorを使ってdispatchする(2)、失敗したら失敗した事実やエラーオブジェクトを別のAction Creatorを使ってdispatchする(3)、といった流れになっています。

 リスト10の中でのdispatchに使っているAction Creatorの実装は、リスト11の通りです。至って普通のStandard Flux Actionsになっています。

[リスト11]src/actions/posts.js
/** 最新の全投稿データをアプリ内の状態に反映する */
function updatePosts(latestPosts) {
  return {
    type: "UPDATE_POSTS",
    payload: {
      posts: latestPosts
    }
  }
}
/** 初期化に失敗した */
function initFailed(error) {
  return {
    type: "INIT_FAILED",
    payload: error,
    error: true
  }
}
/** 送信に失敗した */
function sendingFailed(error) {
  return {
    type: "SENDING_FAILED",
    payload: error,
    error: true
  }
}

 また、リスト11のActionを受け取るReducerはリスト12のようになります。

[リスト12]src/reducers/posts.js
const defaultState = { posts: [], error: false };
const posts = (state = defaultState, action) => {
  switch(action.type) {
    case 'UPDATE_POSTS':
      return {
        posts: action.payload.posts,
        error: false, // 前回が失敗しているかもしれないので明示的にfalseを指定する
      };
    case 'INIT_FAILED':
      return {
        ...state,
        error: true
      };
    case 'SENDING_FAILED':
      return {
        ...state,
        error: true
      }
    default:
      return state;
  }
};
export default posts;

 Reducerの特筆すべき点としては、エラーがあったかどうかを表すerrorプロパティを持っていることです。リスト4でも触れた通り、Reducerでエラーの有無を管理しておくと、コンポーネント側でエラーがあったことをユーザーに伝える際に便利です。

 これで非同期処理を伴うReduxが一回り分実装できました。図5でブラックボックスになっていた部分は、図6の流れで実装されたことになります。

図6:画面を初期化する際のシーケンス図
図6:画面を初期化する際のシーケンス図

 React側からのdispatch呼び出しをそのまま使うのではなく、一度別の処理を行ってから改めてdispatchを行う、という特徴をつかんでおきましょう。

まとめ

 今回は、非同期処理の手法について紹介しました。Reduxの非同期処理にはデファクトスタンダードが確立しておらず、さまざまな手法が乱立しています。その中でも比較的人気の高いredux-thunkを使った方法を紹介しました。

 次回はMiddlewareを使わない方法の代表例として、ES2017のasync/await構文を使った非同期処理について解説します。

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
基礎からはじめるReact入門連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 中川 幸哉(ナカガワ ユキヤ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。 2026年時点での登録メンバは約50名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/10888 2018/07/05 11:37

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー