redux-sagaチートシート
どうも、Reduxが大好きなCX事業部の片岡です!
今回はRedux使う上で扱いづらい非同期な処理や副作用のある処理の架け橋になるミドルウェアのredux-sagaについての記事です。
redux-sagaとは
Reduxのミドルウェアで、 Reduxでアプリケーションの副作用を扱いやすくできます。
どういうことかというと、データフェッチ等の非同期なものや、ブラウザキャッシュへのアクセス等の不純なものを実行しやすく、管理しやすく、テストしやすくできます。つまり、「肩身の狭いコード達の救世主」となります。
ジェネレーターというES6の機能を使用して、非同期処理の読み取り・書き込み・テストを容易にしています。
特定のActionがDispatchされたタイミングで任意のロジック(API通信等)を非同期処理する時に使うと便利です。
ちょっと書き方が見慣れない感じですが、理解してくると考えた人賢いなと思わず関心してしまいます。
ざっくり用語解説
タスク
タスクはバックグラウンドで実行されているプロセスのようなものです。複数のタスクが並行して実行される可能性があります。タスクを作成する時はジェネレーター関数で作成します。
タスクが実行されるタイミングは主に下記の通りです。
- タスクから別のタスクを非同期で実行(エフェクトクリエイターの
fork
を使用) - タスクから別のタスクを同期的に実行(エフェクトクリエイターの
call
を使用) - 指定したActionがDispatchされたタイミングでタスクを実行(エフェクトクリエイターの
takeEvery
を使用)
エフェクト
エフェクト自体は何をどのように実行するかが記述された単純なJavaScriptオブジェクトです。redux-sagaがエフェクトに書かれている内容を元に、redux-sagaが任意のタイミングでタスクを実行してくれます。
例えば、エフェクトクリエイターのfork
の実行内容をconsole.logしたものです。
{ "@@redux-saga/IO": true, "combinator": false, "type": "FORK", "payload": {...} }
エフェクトクリエイター
エフェクトを作るための関数です。yield
と組み合わせて使います。どのような種類があるかは次で説明します。
よく使うエフェクトクリエイター
大体使うのはこの5つです。これだけ覚えておけばredux-sagaの恩恵に授かることができます。
fork
別のタスクを非同期で実行します。主に他のジェネレーター関数を実行するのに使うことが多い印象です。
call
別のタスクを同期的に実行します。主にAPIへリクエストを送る処理を実行するのに使うことが多い印象です。
select
Selectorを指定して現在のStateを取得します。これを使うことでActionのpayload以外の情報を取得できるので便利です!
put
指定したActionをDispatchします。これを組み合わせることで「State1を更新後、State2を更新する」という離れ業ができちゃいます!
takeEvery
指定したActionがDispatchされたタイミングでタスクを実行します。
他にも沢山のエフェクトクリエイターがあるので詳細は一覧は公式ドキュメントをご覧ください。
https://redux-saga.js.org/docs/api/#effect-creators
活用例
活用例その1:実際にredux-sagaを使う
流れとしては下記の通りになります。
- 各タスクを作る
- 各タスクを呼ぶrootSagaを設ける
- Reduxのミドルウェアとしてredux-sagaを登録し、rootSagaを呼ぶ
1. 適当にタスクを作ります
ひとまず適当にタスクを作ります。
import { fork, put, takeEvery } from 'redux-saga/effects' // 適当にconsole.logするタスク function* hogeSaga() { console.log('hoge') } // 適当にHUGAをDispatchするタスク function* hugaSaga() { yield put({ type: 'HUGA' }) } // CLICK_HOGEに一致するActionがDispatchされたらhugaSagaを実行する function* watchHogeSaga() { yield takeEvery('CLICK_HOGE', hugaSaga) } // 適当なタスク達を実行する export function* hogeHugaSaga() { yield fork(hogeHugaSaga) yield fork(watchHogeSaga) }
2. 作ったタスク達を呼ぶrootSagaを作ります
各タスク達を集約して呼ぶrootSagaを作ります。一つ一つfork
で呼ぶ方法以外に、all
で一括して呼ぶこともできます。
import { fork } from 'redux-saga/effects' import { hogeHugaSaga } from './hogeHugaSagas' export function* rootSaga() { yield fork(hogeHugaSaga) }
3. Reduxにミドルウェアとしてredux-sagaを登録する
Reduxのミドルウェアとしてredux-sagaを登録します。この際にrootSagaを実行するようにします。
import { createStore, applyMiddleware } from 'redux' import createSagaMiddleware from 'redux-saga' import { rootSaga } from './sagas' // ... const sagaMiddleware = createSagaMiddleware() const store = createStore( reducer, applyMiddleware(sagaMiddleware) ) sagaMiddleware.run(rootSaga)
活用例その2:APIアクセスしてStateに格納する
流れとしては下記の通りになります。
- アプリケーション側で
CLICK_HOGE
をDispatchする - takeEveryで
CLICK_HOGE
がDispatchされたことを検知し、以下の処理が含まれたfetchHoge
を実行する - selectで格納されているトークンを取得
- リクエスト前に
FETCH_HOGE_REQUEST
にputでDispatchする - 取得したトークンとともにcallでAPIへリクエストを行う
- 成功時、リクエスト内容を
FETCH_HOGE_SUCCESS
にputでDispatchしてStateに格納します - 失敗時はエラー内容をReducerのない
FETCH_HOGE_FAILURE
にDispatchするとRedux DevToolsでデバッグしやすくなります
APIにリクエストする前にFETCH_HOGE_REQUEST
をDispatchすることで、処理がコケた場合どこでコケたかわかるのと、APIからレスポンスが返ってきて格納するまでの時間がRedux DevToolsで追えるのでおすすめです!
ちなみにActionの粒度は、
- アプリケーションに関するActionは「ユーザが現実世界で実際に行ったこと」を粒度+命名規則に
- redux-sagaに関するActionは「アプリケーションからSaga」「SagaからRedux」「ReduxからSaga」の粒度
を目処に作るといいです。Redux自体は副作用のあるActionを作れないのでこのような粒度で小分けにしてredux-saga側で吸収するのがおすすめです!
※サンプルコードを用意するのが面倒くさくなったので許してヒヤシンス!
活用例その3:副作用を伴うタスクを実行する
例えば、「APIからデータを取得中にローディングを表示したい」みたいな副作用のある処理の場合だと下記のようになります。
- APIにアクセス前にローディング状態(isLoading)をtrueにする
- APIにアクセスした内容を格納する
- ローディング状態(isLoading)をfalseにする
このような副作用を伴うStateの更新もできるのでいいですね!
「何かがDispatchされたらあれもこれも変更する」みたいな時に真価を発揮します。
他にもこんなことができそうです。
- リセットボタン押下時、特定のロジックに基づいてState達を初期化する
- 複雑なアニメーションのタイミングを管理する(例:ページ遷移時、コンテンツをフェードアウトしてあしらい1・あしらい2・あしらい3をスライドインしてコンテンツ取得できるまで待機し、あしらい達をスライドアウトしてコンテンツをフェードインする)
活用例その4:データを加工してStateに格納したい
個人的にはデータを加工してStateに格納したい場合はredux-sagaで吸収するべきだと思っています。
ActionCreatorとReducerは受け取った値を左から右に受け流すシンプルな設計であると破綻しづらくなると思います。
最後に
この記事の「よく使うアクションクリエイター」は本当によく使うのでこの記事をブックマークしたり、印刷して額に入れて飾って末代まで受け継いで頂いたりしてもらえばうれしいなと思います!
みんなも使ってみてくれるかな?
\いいともー!/