redux-sagaとは?
Reduxにタスクという概念を持ち込むためのライブラリです。Redux-sagaに依頼することで間接的にタスクを実行してくれます。
仕組み
非同期処理を別々のスレッド(タスク)のイベントループのように切り出してそこに副作用(主に非同期処理の責務)を押し付けるスタイル。
タスク
redux-sagaのタスクには2種類あります。
rootSagaタスク
ReduxのStoreが作成された後、redux-sagaのMiddlewareが起動するタイミングで1回だけ呼び出されます。
役割
fork関数を使って子タスクの起動をします。というか、それ以外には基本的にやることはないです。forkを使って起動した全てのタスクが終了するまで待ちます。
子タスク(「handleXXX」)
rootSagaがfork関数を使って呼び出されるタスクです。呼び出されるタスクはwhileで無限ループさせておきます。
redux-sagaはなぜGenerator関数で書くのか?
処理を滞らせないためです。JavaScriptはシングルスレッドなので複数タスクの同時処理をすると処理が滞ったりすることが普通なのですが、yieldで止めておけば滞ることなく複数タスクを制御することが可能になります。
redux-thunkとの違い。
redux-thunkはActionCreatorに非同期処理のコードやロジックを詰め込むことになります。
対して、redux-sagaは非同期処理を記述する専用の仕組みであるタスクに書きます。なので、actionCreatorを「Actionオブジェクトを返すだけ」という本来の姿に戻せます。
何に悩んでいたら使った方が良いのか?
- 特定のActionを待って、何か別のActionをdispatchしたい場合
- 通信処理の完了を待って、別の通信処理を開始したい場合
- 初期化時にデータを読み込ませたい場合
- 頻繁に発生するActionをバッファしてdispatchしたい場合
メリット
- モックを使わなくてよくなるのでテストコードを書きやすい。
- 小さい単位にコードを分割して再利用性を上げれる。
- actionCreatorを「Actionオブジェクトを返すだけ」という本来の姿に戻せる。
設定
redux-sugaはreduxのミドルウェアなのでStoreに下記のように登録する必要があります。
1 2 3 4 5 6 7 8 |
import { createStore, applyMiddleware } from 'redux'; import createSagaMiddleware from 'redux-saga'; import rootSaga from './sagas'; const sagaMiddleware = createSagaMiddleware(); const store = createStore(reducers, applyMiddleware(sagaMiddleware)); sagaMiddleware.run(rootSaga); |
タスクを書くための関数
select
Stateから必要なデータを取り出す。こんな感じで取り出せます。
1 |
const { 変数 } = yield select(state => state.取り出したい値) |
put
Actionをdispatchする。下記例では、APIにリクエストした戻り値であるpayloadを「A」というactionにdispatchしています。
1 |
yield put(A(payload)); |
用途
- APIから受け取ったdataで値を更新する。
- error処理を行うとき。
take
実行中のsagaが完了するまで、そのactionがdispatchされるタイミングを監視する。Actionを待つ、イベントの発生を待つ。下記例では「REQUEST_XXX」Actionがdispatchされるのを待ちます。
1 |
const action = yield take(REQUEST_XXX); |
用途
- ユーザーの削除
- 進行中の処理が完了するのを待ち、別の処理を行う。
takeEvery
actionがdispatchされる度にタスクが起動する。dispatchさせるたびに監視させたい処理
1 |
takeEvery(Actionのタイプ, handleXXX) |
用途
APIからデータをリストで取得したい場合
takeLatest
actionが複数回dispatchされる可能性がある時に、現在実行中の最新のsagaのみ取得する処理。すでに同じactionによって起動したタスクが終了していない場合は、そのタスクはキャンセルされる。(つまり、これを使えば1ユーザーの二重送信とかは防げるということか。)
1 |
takeLatest(Actionのタイプ, handleXXX) |
用途
- レコードの作成や更新
- 同時に複数のコンポーネントから同じAPIを参照する場合
call
関数またはPromiseの完了を待機、次のコードを実行したいとき。下記のような感じでAPIの呼び出しに使われます。
1 |
const payload = yield call(api.getXXX, 引数) |
all
Promise.Allとと同じ動きです。
1 2 3 4 5 6 7 |
import { all } from 'redux-saga/effects'; export default function* rootSaga() { yield all([ ...XXXSagas, ]); }; |
fork
別タスクを開始する。下記例では「handleXXX」というタスクを起動している。
1 |
yield fork(handleXXX) |
fork関数の戻り値は「ただのオブジェクト」です。
join
別タスクの終了を待つ。
この記事へのコメントはありません。