SHOEISHA iD

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

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

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

基礎からはじめるReact入門

Reduxで非同期処理を行う~ES2017のasync/await構文を使って実装する方法

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

async/await構文で掲示板アプリを作り直す

 本記事では、第9回で非同期処理に対応させた掲示板アプリを、async/await構文を用いて作り直します。といっても、変わった点はあまり多くありません。srcフォルダへの変更内容をまとめたのがリスト6です。

[リスト6]今回扱うファイルの一覧
src/
  actions/
    posts.js (▲)
  api/
    ApiClient.js
  reducers/
    index.js
    posts.js
  App.css
  App.js (▲)
  Form.js
  index.js (▲)
  List.js

 ▲が付いているファイルが、今回変更があったものです。Form.jsやList.jsだけでなく、今回はreducersフォルダにも変更がありません。第9回でもお話した通り、Reduxと非同期処理の関係についての話題は「非同期処理の結果をいつどうdispatchするか」に収束します。そのため、dispatch後の挙動であるreducerには影響が起こらないのです。

Middlewareを使わない

 まずは、index.jsを見てみましょう(リスト7)。

[リスト7]src/index.js
const store = createStore(rootReducer);

 もともとredux-thunk以外のMiddlewareを使っていなかったので、applyMiddlewareを実行する必要がなくなり、従来通りのcreateStoreの使い方で動くようになりました。

コンポーネントにdispatchをさせない

 次にApp.jsの挙動がどうなるのか見てみましょう(リスト8)。

[リスト8]src/App.js
import { initPosts, sendNewPost } from "./actions/posts";

class App extends Component {
  componentDidMount() {
    // 投稿を表示するための初期データをサーバーに請求する
    initPosts(this.props.dispatch); // (1)
  }
  /** Formが作成した投稿を保存する処理 */
  saveNewPost(newPost) {
    // サーバーにデータを送る
    sendNewPost(this.props.dispatch, newPost); // (2)
  }
  // 略
}

 これまではコンポーネント内の適切なタイミングでdispatchを実施するスタイルを取っていましたが、Middlewareがない環境でdispatchすると、何の処理も挟むことができないままReducerに届いてしまいます。これは望んだ動作ではありません。そのため(1)や(2)のように、Action Creator(のような別物)を実行する際に、dispatch関数を引数で渡して、initPostsやsendNewPostの内部実装にdispatchのタイミングを委ねる方式にしました。App.jsへの変更はこれだけです。

非同期処理をasync functionで記述する

 それでは、コンポーネントから呼び出された、initPostsやsendNewPostの内部実装を見てみましょう(リスト9)。

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

 async functionで再実装されたinitPostsやsendNewPostの内部では、awaitを使ってAPIの呼び出しを待てるようになりました。成功・失敗の挙動の際にdispatchするactionについては、第9回で紹介したものとまったく同じものを使っています。

 App.jsから呼び出した場合は各関数からPromiseが戻り値として返っていますが、今回の使い方ではawaitしながらdispatchを行うのが目的なので、あえてApp.js側でthenを使うことはしていません。成功・失敗を受けて表示を更新するのは、あくまでもRedux経由で行いましょう、というスタンスです。

 これで、async/await構文による非同期処理を組み込むことができました。

[コラム]実はredux-thunkと併用してもよい

 「redux-thunk + Promise」を使う方法とasync/awaitを使う方法をそれぞれ別個に紹介してきましたが、実はこれらは併用することも可能です(リスト10)。

[リスト10]redux-thunkとasync/awaitを併用する
// src/App.js
this.props.dispatch(initPosts());

// src/actions/posts.js
export function initPosts() {
  return async (dispatch) => { // (1)
    try {
      const posts = await ApiClient.fetchPosts();
      dispatch(updatePosts(posts));
    } catch (e) {
      dispatch(initFailed(e));
    }
  }
}

 (1)のように、ラムダ式の頭にasyncキーワードを付けるのがコツです。これで、redux-thunkを使ってdispatchを受け取るためのラムダ式をasync functionにすることができ、内部でawaitを使えるようになります。外側のinitPosts関数自体にasyncキーワードを付けてもラムダ内でawaitが使えるようになるわけではないので、ご注意ください。

 redux-thunkをはじめとしたMiddlewareたちは、async/awaitと排他的なツールではありません。適切に組み合わせながら、開発しやすい環境を組み立てていきましょう。

まとめ

 2回にわたってReduxの非同期処理について紹介してきました。「実際に非同期処理をしているのはPromiseやasync/awaitであって、Reduxは何もしていないじゃないか」と思った方は、Reduxへの理解度がかなり高まっています。大切なのはReactコンポーネント内に非同期処理を書かないことだけで、そうでない場所でPromiseやasync/awaitで非同期処理を行い、最後にdispatchが呼び出せれば、本質的にはどんな書き方でもよいのです。皆さんも自分にあった非同期処理のスタイルを模索してみてください。

 次回は、Reduxで書いたコードをユニットテストする手法をご紹介します。

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

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
基礎からはじめる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/10889 2018/07/17 14:00

イベント

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

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

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

メールバックナンバー