対象読者
- JavaScriptとWeb開発の基礎に理解がある方
- Reactに興味/関心があり、これから学び始める方
前提環境
筆者の検証環境は以下の通りです。
- macOS High Sierra 10.13.3
- Node.js v8.9.4/npm 5.6.0
- React 16.3.1
- redux 3.7.2
- react-redux 5.0.7
- redux-thunk 2.2.0
非同期処理とRedux
本格的なGUIアプリケーションを開発する上で、避けては通れないのが非同期処理です。非同期処理はネットワークやデータベースなどの時間がかかる処理を扱う場合によく用いられる手法で、GUIアプリケーションが外の世界にアクセスしようとする場合には、ほぼ必須となる概念です。
非同期処理についておさらい
まずは、非同期処理について簡単におさらいしておきましょう。「非」同期的な処理であると言うからには、同期的な処理もあるということなので、まずは同期的な処理について確認します。同期的な処理とは、上から下に向かってスコープ内の式をひとつひとつ順番に実行し、前の式が終わってから次の式を実行するという、至って普通の実行方式です(図1)。同期的な処理によって書かれた関数は、return
によって値を返すことができます。
これに対して、非同期的な処理は、スコープ内に書かれたすべての処理がひと続きに実行されるわけではありません。この説明ではややこしいので、図で表します。図2に示したasyncFunction関数は、そのスコープ内にdoAwesome関数の呼び出しを含んでいます。doAwesome関数にはコールバック関数が渡されており、引数c
を使った処理がコールバック関数の中で行われています。
asyncFunction関数は、まず同期的に実行できる式を実行していきます。図2の例では、①と②の部分が同期的に実行できる処理です。この時点で行われるのは、「コールバック関数のオブジェクトを生成してdoAwesomeの第3引数として渡す」処理までで、コールバック関数自体は実行されません。
では、コールバック関数はいつ実行されるのかというと、これはdoAwesomeの内部で呼び出されたときなので、いつになるのかは分かりません。50ミリ秒後なのか3秒後なのかは分かりませんが、おもむろにコールバック関数が実行されます(図3)。
③のように同期的な処理とは別のタイミングでの呼び出し方を「非同期的な呼び出し」と呼ぶことがあります。つまり、コールバック関数の実装はいつ呼ばれても問題ないように作っておくことが求められます。また、こういった作りになっている都合上、コールバック関数内に実装された処理の結果をasyncFunction関数の結果として返すことはできません。
同期的な処理に比べると考慮すべきことが増えてしまいますが、UIのイベント処理のような「早めに終わらせないと画面が止まってしまう」といった処理においてはとても有用な考え方なので、ぜひ活用するべきでしょう。幸いなことに、JavaScriptの文化圏では時間のかかる処理をPromiseによって非同期化することが多くの分野で標準化されています。リスト1は通信を行うためのグローバル関数であるfetch
を使った場合の例です。
fetch("https://example.com/api/hoge") // Promiseオブジェクトが返却される .then(response => { // 通信が成功した場合の処理 }) .catch(error => { // エラーが発生した場合の処理 })
時間のかかる処理を書く場合には、非同期処理にすることを強制されるので、通信などの関数を覚えていく中で、自然と非同期処理が身についていくことでしょう。
Reduxの非同期処理はどこで行うのか
Reduxに非同期処理を組み込んでいく上で、まず確認しておかなければならないのは、Reducerの特性です。前回紹介した通り、Reducerは既存の状態とActionを受け取り、新しいデータを生み出して返却する関数群でした。データを返却する必要があるということは、Reducerの関数内で非同期処理を行ったとしても、その結果を返却することができません。Reducerの中に非同期処理を記述しても、意味が無いのです。
Reducerの処理によって生み出された新しい状態はそのままViewの更新に使われるため、Reducer以降に非同期処理を行える場所はありません。Reducerが実行されるより前の段階のどこか、ということになります。では、どこで非同期処理を行うのが適切なのかというと、デファクトスタンダードな方法は存在していません。大まかには次の2つのうちどちらかの方法を採用することになります。
- Middlewareを使う方法
- Middlewareを使わない方法
Middlewareを使った場合は、「dispatch
関数を実行した後で、Reducerが実行される前」のタイミングで非同期処理を行います。一方で、Middlewareを使わない場合は、「非同期処理が終わってからdispatch
関数を初めて呼ぶ」といった順序になります。本記事では、前者の例としてredux-thunkというMiddlewareの使い方を紹介します。