redux-sagaチートシート

redux-sagaをこれから使う・使っている人の為のチートシートです。redux-sagaを触る際、この記事を見るとスラスラと書けるようになります。
2020.12.15

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

どうも、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を使う

流れとしては下記の通りになります。

  1. 各タスクを作る
  2. 各タスクを呼ぶrootSagaを設ける
  3. Reduxのミドルウェアとしてredux-sagaを登録し、rootSagaを呼ぶ

1. 適当にタスクを作ります

ひとまず適当にタスクを作ります。

/sagas/hogeHugaSagas.ts

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で一括して呼ぶこともできます。

/sagas/index.ts

import { fork } from 'redux-saga/effects'
import { hogeHugaSaga } from './hogeHugaSagas'

export function* rootSaga() {
  yield fork(hogeHugaSaga)
}

3. Reduxにミドルウェアとしてredux-sagaを登録する

Reduxのミドルウェアとしてredux-sagaを登録します。この際にrootSagaを実行するようにします。

store.ts

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に格納する

流れとしては下記の通りになります。

  1. アプリケーション側でCLICK_HOGEをDispatchする
  2. takeEveryでCLICK_HOGEがDispatchされたことを検知し、以下の処理が含まれたfetchHogeを実行する
  3. selectで格納されているトークンを取得
  4. リクエスト前にFETCH_HOGE_REQUESTにputでDispatchする
  5. 取得したトークンとともにcallでAPIへリクエストを行う
  6. 成功時、リクエスト内容をFETCH_HOGE_SUCCESSにputでDispatchしてStateに格納します
  7. 失敗時はエラー内容を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からデータを取得中にローディングを表示したい」みたいな副作用のある処理の場合だと下記のようになります。

  1. APIにアクセス前にローディング状態(isLoading)をtrueにする
  2. APIにアクセスした内容を格納する
  3. ローディング状態(isLoading)をfalseにする

このような副作用を伴うStateの更新もできるのでいいですね!

「何かがDispatchされたらあれもこれも変更する」みたいな時に真価を発揮します。

他にもこんなことができそうです。

  • リセットボタン押下時、特定のロジックに基づいてState達を初期化する
  • 複雑なアニメーションのタイミングを管理する(例:ページ遷移時、コンテンツをフェードアウトしてあしらい1・あしらい2・あしらい3をスライドインしてコンテンツ取得できるまで待機し、あしらい達をスライドアウトしてコンテンツをフェードインする)

活用例その4:データを加工してStateに格納したい

個人的にはデータを加工してStateに格納したい場合はredux-sagaで吸収するべきだと思っています。

ActionCreatorとReducerは受け取った値を左から右に受け流すシンプルな設計であると破綻しづらくなると思います。

最後に

この記事の「よく使うアクションクリエイター」は本当によく使うのでこの記事をブックマークしたり、印刷して額に入れて飾って末代まで受け継いで頂いたりしてもらえばうれしいなと思います!

みんなも使ってみてくれるかな?

\いいともー!/