APIリファレンス
ミドルウェア API
createSagaMiddleware(options)
Reduxミドルウェアを作成し、SagaをRedux Storeに接続します。
options: Object- ミドルウェアに渡すオプションのリスト。現在サポートされているオプションは次のとおりです。context: Object- Sagaのコンテキストの初期値。sagaMonitor: SagaMonitor - Saga Monitorが提供されている場合、ミドルウェアは監視イベントをモニターに配信します。onError: (error: Error, { sagaStack: string })- 提供されている場合、ミドルウェアはSagaからのキャッチされないエラーでそれを呼び出します。キャッチされない例外をエラー追跡サービスに送信するのに役立ちます。effectMiddlewares: Function [] - すべてのエフェクトをインターセプトし、独自に解決して次のミドルウェアに渡すことができます。詳細な例については、このセクションを参照してくださいchannel: 提供されている場合、ミドルウェアは、デフォルトのstdChannel()の代わりにこのチャネルを
takeおよびputエフェクトに使用します。
例
以下では、Storeに新しいメソッドrunSagaを追加する関数configureStoreを作成します。次に、メインモジュールで、このメソッドを使用してアプリケーションのルートSagaを開始します。
configureStore.js
import createSagaMiddleware from 'redux-saga'
import reducer from './path/to/reducer'
export default function configureStore(initialState) {
// Note: passing middleware as the last argument to createStore requires redux@>=3.1.0
const sagaMiddleware = createSagaMiddleware()
return {
...createStore(reducer, initialState, applyMiddleware(/* other middleware, */sagaMiddleware)),
runSaga: sagaMiddleware.run
}
}
main.js
import configureStore from './configureStore'
import rootSaga from './sagas'
// ... other imports
const store = configureStore()
store.runSaga(rootSaga)
注記
sagaMiddleware.runメソッドの詳細については、下記を参照してください。
middleware.run(saga, ...args)
sagaを動的に実行します。applyMiddlewareフェーズ後のみにSagaを実行するために使用できます。
saga: Function: ジェネレーター関数args: Array<any>:sagaに提供される引数
このメソッドは、タスク記述子を返します。
注記
sagaは、ジェネレーターオブジェクトを返す関数である必要があります。次に、ミドルウェアはジェネレーターを反復処理し、生成されたすべてのエフェクトを実行します。
sagaは、ライブラリによって提供されるさまざまなエフェクトを使用して、他のSagaを開始することもできます。以下で説明する反復処理は、すべての子Sagaにも適用されます。
最初の反復で、ミドルウェアはnext()メソッドを呼び出して、次のエフェクトを取得します。次に、ミドルウェアは、以下のエフェクトAPIで指定されているように、生成されたエフェクトを実行します。一方、エフェクトの実行が終了するまで、ジェネレーターは一時停止されます。実行の結果を受け取ると、ミドルウェアはジェネレーターでnext(result)を呼び出し、取得した結果を引数として渡します。このプロセスは、ジェネレーターが正常に終了するか、何らかのエラーをスローするまで繰り返されます。
実行がエラーになった場合(各エフェクトクリエーターで指定されているように)、代わりにジェネレーターのthrow(error)メソッドが呼び出されます。ジェネレーター関数が現在のyield命令を囲むtry/catchを定義している場合、基盤となるジェネレーターランタイムによってcatchブロックが呼び出されます。ランタイムは、対応するfinallyブロックも呼び出します。
Sagaがキャンセルされた場合(手動または提供されたエフェクトを使用して)、ミドルウェアはジェネレーターのreturn()メソッドを呼び出します。これにより、ジェネレーターは直接finallyブロックにスキップします。
エフェクトクリエーター
注記
- 以下の各関数は、プレーンなJavaScriptオブジェクトを返し、実行は行いません。
- 実行は、上記で説明した反復処理中にミドルウェアによって実行されます。
- ミドルウェアは、各エフェクトの説明を調べて、適切なアクションを実行します。
take(pattern)
Storeで指定されたアクションを待機するようにミドルウェアに指示するエフェクトの説明を作成します。patternに一致するアクションがディスパッチされるまで、ジェネレーターは一時停止されます。
yield take(pattern)の結果は、ディスパッチされるアクションオブジェクトです。
patternは、次のルールを使用して解釈されます。
引数なしまたは
'*'でtakeが呼び出された場合、ディスパッチされたすべてのアクションが一致します(例:take()はすべてのアクションに一致します)。それが関数の場合、
pattern(action)がtrueの場合、アクションが一致します(例:take(action => action.entities)は、(真偽値の)entitiesフィールドを持つすべてのアクションに一致します)。注:パターン関数に
toStringが定義されている場合、代わりにaction.typeがpattern.toString()に対してテストされます。これは、redux-actやredux-actionsなどのアクションクリエーターライブラリを使用している場合に役立ちます。文字列の場合、
action.type === patternの場合、アクションが一致します(例:take(INCREMENT_ASYNC))。配列の場合、配列内の各項目は前述のルールと一致するため、文字列と関数述語の混在配列がサポートされます。最も一般的なユースケースは文字列の配列であるため、
action.typeは配列内のすべての項目と照合されます(例:take([INCREMENT, DECREMENT])。これは、INCREMENTまたはDECREMENT型のいずれかのアクションに一致します)。
ミドルウェアは、特別なアクションENDを提供します。ENDアクションをディスパッチすると、指定されたパターンに関係なく、takeエフェクトでブロックされたすべてのSagaが終了します。終了したSagaにまだ実行中のフォークされたタスクがある場合、すべてのタスクが終了するのを待ってからタスクを終了します。
takeMaybe(pattern)
take(pattern)と同じですが、ENDアクションでSagaを自動的に終了しません。代わりに、takeエフェクトでブロックされたすべてのSagaがENDオブジェクトを取得します。
注記
takeMaybeは、FPのアナロジーからその名前が付けられました。ACTION(自動処理付き)の戻り値の型を持つ代わりに、Maybe(ACTION)の型を持つことができるようなものです。これにより、両方のケースを処理できます。
Just(ACTION)がある場合(アクションがある場合)NOTHINGの場合(チャネルが閉じられた*)。つまり、ENDをマッピングする何らかの方法が必要です。
- 内部的には、
dispatchされたすべてのアクションはstdChannelを通過しており、dispatch(END)が発生すると閉じられます。
take(channel)
ミドルウェアに、提供されたチャネルからの指定されたメッセージを待機するように指示するエフェクトの説明を作成します。チャネルが既に閉じられている場合、ジェネレーターはtake(pattern)について上記で説明したのと同じプロセスに従ってすぐに終了します。
takeMaybe(channel)
take(channel)と同じですが、ENDアクションでSagaを自動的に終了しません。代わりに、takeエフェクトでブロックされたすべてのSagaがENDオブジェクトを取得します。詳細については、こちらを参照してください。
takeEvery(pattern, saga, ...args)
Storeにディスパッチされたpatternに一致する各アクションでsagaを生成します。
pattern: String | Array | Function- 詳細については、take(pattern)のドキュメントを参照してください。saga: Function- ジェネレーター関数args: Array<any>- 開始されたタスクに渡される引数。takeEveryは、受信したアクションを引数リストに追加します(つまり、アクションはsagaに提供される最後の引数になります)。
例
次の例では、基本的なタスクfetchUserを作成します。ディスパッチされた各USER_REQUESTEDアクションで新しいfetchUserタスクを開始するために、takeEveryを使用します。
import { takeEvery } from `redux-saga/effects`
function* fetchUser(action) {
...
}
function* watchFetchUser() {
yield takeEvery('USER_REQUESTED', fetchUser)
}
注記
takeEveryは、takeとforkを使用して構築された高レベルのAPIです。以下は、低レベルのエフェクトを使用してヘルパーを実装する方法です。
const takeEvery = (patternOrChannel, saga, ...args) => fork(function*() {
while (true) {
const action = yield take(patternOrChannel)
yield fork(saga, ...args.concat(action))
}
})
takeEveryを使用すると、同時アクションを処理できます。上記の例では、USER_REQUESTEDアクションがディスパッチされると、前のfetchUserがまだ保留中の場合でも、新しいfetchUserタスクが開始されます(たとえば、ユーザーがLoad Userボタンを連続して2回クリックした場合、最初のクリックで起動したfetchUserがまだ終了していないときに、2回目のクリックでUSER_REQUESTEDアクションがディスパッチされます)。
takeEvery は、タスクからの順不同のレスポンスを処理しません。タスクが開始された順序で終了するという保証はありません。順不同のレスポンスを処理するには、以下の takeLatest を検討してください。
takeEvery(channel, saga, ...args)
引数としてチャンネルを渡すこともでき、動作は takeEvery(pattern, saga, ...args) と同じです。
takeLatest(pattern, saga, ...args)
ストアにディスパッチされたアクションが pattern に一致するたびに、saga をフォークします。また、以前に開始された saga タスクがまだ実行中の場合は、自動的にキャンセルします。
アクションがストアにディスパッチされるたびに、このアクションが pattern に一致する場合、takeLatest はバックグラウンドで新しい saga タスクを開始します。以前(実際のアクションの前にディスパッチされた最後のアクションで)saga タスクが開始され、このタスクがまだ実行中の場合、タスクはキャンセルされます。
pattern: String | Array | Function- 詳細については、take(pattern)のドキュメントを参照してください。saga: Function- ジェネレーター関数args: Array<any>- 開始されたタスクに渡される引数。takeLatestは、受信したアクションを引数リストに追加します(つまり、アクションはsagaに提供される最後の引数になります)。
例
次の例では、基本的なタスク fetchUser を作成します。takeLatest を使用して、ディスパッチされた各 USER_REQUESTED アクションで新しい fetchUser タスクを開始します。takeLatest は以前に開始された保留中のタスクをキャンセルするため、ユーザーが複数の連続した USER_REQUESTED アクションを急速にトリガーした場合でも、最後のアクションで終了するようにします。
import { takeLatest } from `redux-saga/effects`
function* fetchUser(action) {
...
}
function* watchLastFetchUser() {
yield takeLatest('USER_REQUESTED', fetchUser)
}
注記
takeLatest は、take と fork を使用して構築された高レベル API です。以下は、低レベルのエフェクトを使用してヘルパーを実装する方法です。
const takeLatest = (patternOrChannel, saga, ...args) => fork(function*() {
let lastTask
while (true) {
const action = yield take(patternOrChannel)
if (lastTask) {
yield cancel(lastTask) // cancel is no-op if the task has already terminated
}
lastTask = yield fork(saga, ...args.concat(action))
}
})
takeLatest(channel, saga, ...args)
引数としてチャンネルを渡すこともでき、動作は takeLatest(pattern, saga, ...args) と同じです。
takeLeading(pattern, saga, ...args)
ストアにディスパッチされたアクションが pattern に一致するたびに、saga を生成します。タスクを一度生成した後、生成された saga が完了するまでブロックし、その後、再び pattern をリッスンし始めます。
つまり、takeLeading は saga を実行していないときにアクションをリッスンしています。
pattern: String | Array | Function- 詳細については、take(pattern)のドキュメントを参照してください。saga: Function- ジェネレーター関数args: Array<any>- 開始されたタスクに渡される引数。takeLeadingは、受信したアクションを引数リストに追加します(つまり、アクションはsagaに提供される最後の引数になります)。
例
次の例では、基本的なタスク fetchUser を作成します。takeLeading を使用して、ディスパッチされた各 USER_REQUESTED アクションで新しい fetchUser タスクを開始します。takeLeading は開始後の新しいタスクを無視するため、ユーザーが複数の連続した USER_REQUESTED アクションを急速にトリガーした場合でも、先頭のアクションを実行し続けるようにします。
import { takeLeading } from `redux-saga/effects`
function* fetchUser(action) {
...
}
function* watchLastFetchUser() {
yield takeLeading('USER_REQUESTED', fetchUser)
}
注記
takeLeading は、take と call を使用して構築された高レベル API です。以下は、低レベルのエフェクトを使用してヘルパーを実装する方法です。
const takeLeading = (patternOrChannel, saga, ...args) => fork(function*() {
while (true) {
const action = yield take(patternOrChannel);
yield call(saga, ...args.concat(action));
}
})
takeLeading(channel, saga, ...args)
引数としてチャンネルを渡すこともでき、動作は takeLeading(pattern, saga, ...args) と同じです。
put(action)
ストアへのアクションのディスパッチをスケジュールするようにミドルウェアに指示するエフェクト記述を作成します。他のタスクが saga タスクキューで先頭にあるか、まだ進行中の可能性があるため、このディスパッチは即時ではない場合があります。
ただし、非同期フローを持つ他の Redux ミドルウェアがアクションの伝播を遅延させていない限り、ストアが現在のスタックフレームで(つまり、yield put(action) の次のコード行で)更新されることを期待できます。
ダウンストリームのエラー(例:リデューサーからのエラー)は、バブルアップされます。
action: Object- 完全な情報については、Reduxdispatchドキュメントを参照してください
putResolve(action)
put と同様ですが、エフェクトはブロッキングです(promise が dispatch から返される場合、その解決を待機します)ダウンストリームからのエラーをバブルアップします。
action: Object- 完全な情報については、Reduxdispatchドキュメントを参照してください
put(channel, action)
提供されたチャンネルにアクションを put するようにミドルウェアに指示するエフェクト記述を作成します。
channel: Channel-Channelオブジェクト。action: Object- 完全な情報については、Reduxdispatchドキュメントを参照してください
このエフェクトは、put がバッファリングされず、taker によってすぐに消費される場合は、ブロッキングです。これらの taker のいずれかでエラーがスローされた場合、saga にバブルバックされます。
call(fn, ...args)
fn 関数を引数として args を使用して呼び出すようにミドルウェアに指示するエフェクト記述を作成します。
fn: Function- ジェネレーター関数、または Promise を結果として返すか、その他の値を返す通常の関数。args: Array<any>-fnに引数として渡される値の配列
注記
fn は、通常の関数またはジェネレーター関数にすることができます。
ミドルウェアは関数を呼び出し、その結果を調べます。
結果がイテレーターオブジェクトの場合、ミドルウェアは(起動時にミドルウェアに渡された)起動ジェネレーターと同じように、そのジェネレーター関数を実行します。親ジェネレーターは、子ジェネレーターが正常に終了するまで中断され、その場合、親ジェネレーターは子ジェネレーターによって返された値で再開されます。または、子ジェネレーターがエラーで中断するまで、その場合、エラーは親ジェネレーター内でスローされます。
fn が通常の関数であり、Promise を返す場合、ミドルウェアは Promise が解決するまでジェネレーターを中断します。Promise が解決されると、ジェネレーターは解決された値で再開されるか、Promise が拒否された場合は、ジェネレーター内でエラーがスローされます。
結果がイテレーターオブジェクトでも Promise でもない場合、ミドルウェアはすぐにその値を saga に返して、同期的に実行を再開できるようにします。
ジェネレーター内でエラーがスローされた場合、現在の yield 命令を囲む try/catch ブロックがある場合、制御は catch ブロックに渡されます。それ以外の場合、ジェネレーターは発生したエラーで中断し、このジェネレーターが別のジェネレーターによって呼び出された場合、エラーは呼び出し側のジェネレーターに伝播します。
call([context, fn], ...args)
call(fn, ...args) と同じですが、fn に this コンテキストを渡すことをサポートします。これは、オブジェクトメソッドを呼び出すのに便利です。
call([context, fnName], ...args)
call([context, fn], ...args) と同じですが、文字列として fn を渡すことをサポートします。オブジェクトのメソッドを呼び出すのに便利です。例:yield call([localStorage, 'getItem'], 'redux-saga')
call({context, fn}, ...args)
call([context, fn], ...args) と同じですが、オブジェクトのプロパティとして context と fn を渡すことをサポートします。例:yield call({context: localStorage, fn: localStorage.getItem}, 'redux-saga')。 fn は文字列または関数にすることができます。
apply(context, fn, [args])
call([context, fn], ...args) のエイリアス。
cps(fn, ...args)
fn を Node スタイルの関数として呼び出すようにミドルウェアに指示するエフェクト記述を作成します。
fn: Function- Node スタイルの関数。つまり、引数に加えて、fnが終了したときに呼び出す追加のコールバックを受け入れる関数。コールバックは 2 つのパラメーターを受け入れ、最初のパラメーターはエラーを報告するために使用され、2 番目のパラメーターは成功した結果を報告するために使用されますargs: Array<any>-fnの引数として渡される配列
注記
ミドルウェアは、fn(...arg, cb) を呼び出します。cb は、ミドルウェアによって fn に渡されるコールバックです。fn が正常に終了した場合、成功した結果をミドルウェアに通知するために cb(null, result) を呼び出す必要があります。fn でエラーが発生した場合は、エラーが発生したことをミドルウェアに通知するために cb(error) を呼び出す必要があります。
ミドルウェアは、fn が終了するまで中断されたままになります。
cps([context, fn], ...args)
fn に this コンテキストを渡すことをサポートします(オブジェクトメソッドの呼び出し)。
cps({context, fn}, ...args)
cps([context, fn], ...args) と同じですが、オブジェクトのプロパティとして context と fn を渡すことをサポートします。 fn は文字列または関数にすることができます。
fork(fn, ...args)
fn で非ブロッキング呼び出しを実行するようにミドルウェアに指示するエフェクト記述を作成します。
引数
fn: Function- ジェネレーター関数、または Promise を結果として返す通常の関数args: Array<any>-fnに引数として渡される値の配列
Task オブジェクトを返します。
注記
fork は、call と同様に、通常の関数とジェネレーター関数の両方を呼び出すために使用できます。ただし、呼び出しはノンブロッキングであり、ミドルウェアは fn の結果を待っている間、ジェネレーターを中断しません。代わりに、fn が呼び出されるとすぐに、ジェネレーターはすぐに再開します。
fork は、race と並んで、Saga 間の並行処理を管理するための中心的な Effect です。
yield fork(fn ...args) の結果は、Task オブジェクトです。これは、いくつかの便利なメソッドとプロパティを持つオブジェクトです。
フォークされたすべてのタスクは、その親にアタッチされています。親が自身の命令本体の実行を終了すると、フォークされたすべてのタスクが終了するのを待ってから返ります。
エラー伝播
子タスクからのエラーは、自動的に親にバブルアップします。フォークされたタスクでキャッチされないエラーが発生した場合、親タスクは子エラーで中止され、親の実行ツリー全体(つまり、フォークされたタスクと、親の本体がまだ実行中の場合は親の本体によって表されるメインタスク)がキャンセルされます。
フォークされたタスクのキャンセルは、実行中のすべてのフォークされたタスクを自動的にキャンセルします。また、キャンセルされたタスクがブロックされていた現在の Effect もキャンセルされます(存在する場合)。
フォークされたタスクが同期的に失敗した場合(つまり、非同期操作を実行する前に実行直後に失敗した場合)、Task は返されず、代わりに親は可能な限り早く中止されます(親と子の両方が並行して実行されるため、親は子の失敗に気づくとすぐに中止されます)。
デタッチされたフォークを作成するには、代わりに spawn を使用します。
fork([context, fn], ...args)
this コンテキストを使用してフォークされた関数を呼び出すことをサポートします。
fork({context, fn}, ...args)
fork([context, fn], ...args) と同様ですが、context と fn をオブジェクトのプロパティとして渡すことをサポートします。fn は文字列または関数にすることができます。
spawn(fn, ...args)
fork(fn, ...args) と同様ですが、デタッチされたタスクを作成します。デタッチされたタスクは、その親から独立しており、トップレベルのタスクのように動作します。親は、デタッチされたタスクが終了するのを待たずに返り、親またはデタッチされたタスクに影響を与える可能性のあるすべてのイベントは完全に独立しています(エラー、キャンセル)。
spawn([context, fn], ...args)
this コンテキストを使用して関数をスポーンすることをサポートします。
join(task)
ミドルウェアに、以前にフォークされたタスクの結果を待つように指示する Effect の記述を作成します。
task: Task- 以前のforkによって返された Task オブジェクト
注釈
join は、結合されたタスクの同じ結果(成功またはエラー)に解決されます。結合されたタスクがキャンセルされた場合、キャンセルは join エフェクトを実行している Saga にも伝播します。同様に、それらの joiner の潜在的な呼び出し元もキャンセルされます。
join([...tasks])
ミドルウェアに、以前にフォークされたタスクの結果を待つように指示する Effect の記述を作成します。
tasks: Array<Task>- Task は、以前のforkによって返されるオブジェクトです
注釈
これはタスクの配列を join エフェクトでラップし、おおまかに yield tasks.map(t => join(t)) と同等になります。
cancel(task)
ミドルウェアに、以前にフォークされたタスクをキャンセルするように指示する Effect の記述を作成します。
task: Task- 以前のforkによって返された Task オブジェクト
注釈
実行中のタスクをキャンセルするために、ミドルウェアは基になるジェネレーターオブジェクトで return を呼び出します。これにより、タスク内の現在の Effect がキャンセルされ、(定義されている場合は)finally ブロックにジャンプします。
finally ブロック内では、クリーンアップロジックを実行したり、ストアを一貫した状態に保つためのアクションをディスパッチしたりできます(たとえば、ajax リクエストがキャンセルされたときにスピナーの状態を false にリセットするなど)。yield cancelled() を発行することで、finally ブロック内で Saga がキャンセルされたかどうかを確認できます。
キャンセルは、子 saga に下方向に伝播します。タスクをキャンセルすると、ミドルウェアは現在の Effect(タスクが現在ブロックされている場所)もキャンセルします。現在の Effect が別の Saga の呼び出しである場合、それもキャンセルされます。Saga をキャンセルすると、すべてのアタッチされたフォーク(yield fork() を使用してフォークされた saga)がキャンセルされます。これは、キャンセルが、キャンセルされたタスクに属する実行ツリー全体に効果的に影響することを意味します。
cancel はノンブロッキングな Effect です。つまり、それを実行する Saga は、キャンセルを実行した後すぐに再開します。
Promise の結果を返す関数の場合、Promise に [CANCEL] をアタッチすることで、独自のキャンセルロジックをプラグインできます。
次の例は、Promise の結果にキャンセルロジックをアタッチする方法を示しています。
import { CANCEL } from 'redux-saga'
import { fork, cancel } from 'redux-saga/effects'
function myApi() {
const promise = myXhr(...)
promise[CANCEL] = () => myXhr.abort()
return promise
}
function* mySaga() {
const task = yield fork(myApi)
// ... later
// will call promise[CANCEL] on the result of myApi
yield cancel(task)
}
redux-saga は、abort メソッドを使用して jqXHR オブジェクトを自動的にキャンセルします。
cancel([...tasks])
ミドルウェアに、以前にフォークされたタスクをキャンセルするように指示する Effect の記述を作成します。
tasks: Array<Task>- Task は、以前のforkによって返されるオブジェクトです
注釈
これはタスクの配列を cancel エフェクトでラップし、おおまかに yield tasks.map(t => cancel(t)) と同等になります。
cancel()
ミドルウェアに、それが yield されたタスクをキャンセルするように指示する Effect の記述を作成します(自己キャンセル)。これにより、外部(cancel(task))と自己(cancel())の両方のキャンセルに対して、finally ブロック内でデストラクターのようなロジックを再利用できます。
例
function* deleteRecord({ payload }) {
try {
const { confirm, deny } = yield call(prompt);
if (confirm) {
yield put(actions.deleteRecord.confirmed())
}
if (deny) {
yield cancel()
}
} catch(e) {
// handle failure
} finally {
if (yield cancelled()) {
// shared cancellation logic
yield put(actions.deleteRecord.cancel(payload))
}
}
}
select(selector, ...args)
ミドルウェアに、現在のストアの状態に対して提供されたセレクターを呼び出すように指示するエフェクトを作成します(つまり、selector(getState(), ...args) の結果を返します)。
selector: Function- 関数(state, ...args) => args。現在の状態とオプションでいくつかの引数を取り、現在のストアの状態の一部を返しますargs: Array<any>-getStateに加えて、セレクターに渡されるオプションの引数。
select が引数なしで呼び出された場合(つまり、yield select())、エフェクトは状態全体(getState() 呼び出しと同じ結果)で解決されます。
アクションがストアにディスパッチされると、ミドルウェアは最初にアクションをリデューサーに転送し、次に Saga に通知することに注意することが重要です。これは、ストアの状態をクエリすると、アクションが適用された後の状態を取得することを意味します。ただし、この動作は、後続のすべてのミドルウェアが
next(action)を同期的に呼び出す場合にのみ保証されます。後続のミドルウェアがnext(action)を非同期的に呼び出す場合(これは珍しいことですが可能)、saga はアクションが適用される前の状態を取得します。したがって、後続の各ミドルウェアのソースを確認して、next(action)を同期的に呼び出すことを確認するか、redux-saga がコールチェーンの最後のミドルウェアであることを確認することをお勧めします。
注釈
できれば、Saga は自律的であるべきであり、ストアの状態に依存すべきではありません。これにより、Saga コードに影響を与えることなく、状態の実装を簡単に変更できます。saga は、可能な限り、独自の内部制御状態のみに依存する必要があります。ただし、Saga が必要なデータを自身で維持する代わりに、ストアの状態をクエリする方が便利な場合があります(たとえば、Saga がストアによって既に計算された状態を計算するために、一部のリデューサーを呼び出すロジックを複製する場合など)。
たとえば、アプリケーションに次の状態形状があるとします
state = {
cart: {...}
}
セレクター、つまり状態から cart データを抽出する方法を知っている関数を作成できます
./selectors
export const getCart = state => state.cart
次に、select エフェクトを使用して、Saga 内からそのセレクターを使用できます
./sagas.js
import { take, fork, select } from 'redux-saga/effects'
import { getCart } from './selectors'
function* checkout() {
// query the state using the exported selector
const cart = yield select(getCart)
// ... call some API endpoint then dispatch a success/error action
}
export default function* rootSaga() {
while (true) {
yield take('CHECKOUT_REQUEST')
yield fork(checkout)
}
}
checkout は、select(getCart) を使用して、必要な情報を直接取得できます。Saga は getCart セレクターのみと結合されています。cart スライスにアクセスする必要がある Saga(または React コンポーネント)が多数ある場合、それらはすべて同じ関数 getCart と結合されます。そして、状態形状を変更する場合、getCart を更新するだけで済みます。
actionChannel(pattern, [buffer])
ミドルウェアに、イベントチャネルを使用して pattern に一致するアクションをキューに入れるように指示するエフェクトを作成します。オプションで、キューに入れられたアクションのバッファリングを制御するためのバッファを提供できます。
pattern:-take(pattern)の API を参照buffer: Buffer- Buffer オブジェクト
例
次のコードは、すべての USER_REQUEST アクションをバッファリングするためのチャネルを作成します。Saga が call エフェクトでブロックされている場合でも、すべてのブロックされている間に発生するアクションは自動的にバッファリングされます。これにより、Saga は API 呼び出しを一度に 1 つずつ実行します。
import { actionChannel, call } from 'redux-saga/effects'
import api from '...'
function* takeOneAtMost() {
const chan = yield actionChannel('USER_REQUEST')
while (true) {
const {payload} = yield take(chan)
yield call(api.getUser, payload)
}
}
flush(channel)
ミドルウェアに、チャネルからバッファリングされたすべてのアイテムをフラッシュするように指示するエフェクトを作成します。フラッシュされたアイテムは Saga に返されるため、必要に応じて利用できます。
channel: Channel-Channelオブジェクト。
例
function* saga() {
const chan = yield actionChannel('ACTION')
try {
while (true) {
const action = yield take(chan)
// ...
}
} finally {
const actions = yield flush(chan)
// ...
}
}
cancelled()
ミドルウェアに、このジェネレーターがキャンセルされたかどうかを返すように指示するエフェクトを作成します。通常、このエフェクトは finally ブロックで使用して、キャンセル固有のコードを実行します。
例
function* saga() {
try {
// ...
} finally {
if (yield cancelled()) {
// logic that should execute only on Cancellation
}
// logic that should execute in all situations (e.g. closing a channel)
}
}
setContext(props)
ミドルウェアに自身のコンテキストを更新するように指示するエフェクトを作成します。このエフェクトは、sagaのコンテキストを置き換えるのではなく、拡張します。
getContext(prop)
ミドルウェアにsagaのコンテキストの特定のプロパティを返すように指示するエフェクトを作成します。
delay(ms, [val])
実行をmsミリ秒間ブロックし、val値を返すエフェクト記述子を返します。
throttle(ms, pattern, saga, ...args)
patternに一致するStoreにディスパッチされたアクションに対してsagaを生成します。タスクを生成した後も、基盤となるbufferに受信アクションを受け入れ、最大1つ(最新のもの)を保持しますが、同時にmsミリ秒間新しいタスクの生成を保留します(したがって、その名前はthrottleです)。この目的は、タスクを処理中に、指定された期間、受信アクションを無視することです。
ms: Number- アクションの処理開始後にアクションが無視される時間枠の長さ(ミリ秒単位)pattern: String | Array | Function- 詳細については、take(pattern)のドキュメントを参照してください。saga: Function- ジェネレーター関数args: Array<any>- 開始されたタスクに渡される引数。throttleは、受信アクションを引数リストに追加します(つまり、アクションはsagaに提供される最後の引数になります)。
例
次の例では、基本的なタスクfetchAutocompleteを作成します。ディスパッチされたFETCH_AUTOCOMPLETEアクションで新しいfetchAutocompleteタスクを開始するためにthrottleを使用します。ただし、throttleは連続するFETCH_AUTOCOMPLETEをしばらく無視するため、ユーザーがサーバーにリクエストを殺到させないようにします。
import { call, put, throttle } from `redux-saga/effects`
function* fetchAutocomplete(action) {
const autocompleteProposals = yield call(Api.fetchAutocomplete, action.text)
yield put({type: 'FETCHED_AUTOCOMPLETE_PROPOSALS', proposals: autocompleteProposals})
}
function* throttleAutocomplete() {
yield throttle(1000, 'FETCH_AUTOCOMPLETE', fetchAutocomplete)
}
注釈
throttleは、take、fork、およびactionChannelを使用して構築された高レベルのAPIです。低レベルのエフェクトを使用してヘルパーを実装する方法は次のとおりです。
const throttle = (ms, pattern, task, ...args) => fork(function*() {
const throttleChannel = yield actionChannel(pattern, buffers.sliding(1))
while (true) {
const action = yield take(throttleChannel)
yield fork(task, ...args, action)
yield delay(ms)
}
})
throttle(ms, channel, saga, ...args)
引数としてチャネルを処理することもでき、動作はthrottle(ms, pattern, saga, ..args)と同じです。
debounce(ms, pattern, saga, ...args)
patternに一致するStoreにディスパッチされたアクションに対してsagaを生成します。Sagaは、msミリ秒間patternアクションの取得を停止した後に呼び出されます。この目的は、アクションが落ち着くまでsagaの呼び出しを防止することです。
ms: Number-sagaを呼び出すために、最後にpatternアクションが発行されてから経過するミリ秒数を定義します。pattern: String | Array | Function- 詳細については、take(pattern)のドキュメントを参照してください。saga: Function- ジェネレーター関数args: Array<any>- 開始されたタスクに渡される引数。debounceは、受信アクションを引数リストに追加します(つまり、アクションはsagaに提供される最後の引数になります)。
例
次の例では、基本的なタスクfetchAutocompleteを作成します。debounceを使用して、少なくとも1000ミリ秒間FETCH_AUTOCOMPLETEイベントを受信しなくなるまで、fetchAutocomplete sagaの呼び出しを遅延させます。
import { call, put, debounce } from `redux-saga/effects`
function* fetchAutocomplete(action) {
const autocompleteProposals = yield call(Api.fetchAutocomplete, action.text)
yield put({type: 'FETCHED_AUTOCOMPLETE_PROPOSALS', proposals: autocompleteProposals})
}
function* debounceAutocomplete() {
yield debounce(1000, 'FETCH_AUTOCOMPLETE', fetchAutocomplete)
}
注釈
debounceは、take、delay、race、およびforkを使用して構築された高レベルのAPIです。低レベルのエフェクトを使用してヘルパーを実装する方法は次のとおりです。
const debounce = (ms, pattern, task, ...args) => fork(function*() {
while (true) {
let action = yield take(pattern)
while (true) {
const { debounced, latestAction } = yield race({
debounced: delay(ms),
latestAction: take(pattern)
})
if (debounced) {
yield fork(task, ...args, action)
break
}
action = latestAction
}
}
})
debounce(ms, channel, saga, ...args)
引数としてチャネルを処理することもでき、動作はdebounce(ms, pattern, saga, ..args)と同じです。
retry(maxTries, delay, fn, ...args)
ミドルウェアに、引数としてargsを使用して関数fnを呼び出すように指示するエフェクト記述を作成します。失敗した場合、試行回数がmaxTries未満の場合、delayミリ秒後に別の呼び出しを試みます。
maxTries: Number- 最大呼び出し回数。delay: Number-fn呼び出し間の時間枠の長さ(ミリ秒単位)。fn: Function- 結果としてPromiseを返すジェネレータ関数、または通常の関数、またはその他の値。args: Array<any>-fnに引数として渡される値の配列
例
次の例では、基本的なタスクretrySagaを作成します。retryを使用して、APIを10秒間隔で3回フェッチしようとします。requestが最初に失敗した場合、retryは呼び出し回数が3未満の間、requestをもう一度呼び出します。
import { put, retry } from 'redux-saga/effects'
import { request } from 'some-api';
function* retrySaga(data) {
try {
const SECOND = 1000
const response = yield retry(3, 10 * SECOND, request, data)
yield put({ type: 'REQUEST_SUCCESS', payload: response })
} catch(error) {
yield put({ type: 'REQUEST_FAIL', payload: { error } })
}
}
注釈
retryは、delayとcallを使用して構築された高レベルのAPIです。低レベルのエフェクトを使用してヘルパーを実装する方法は次のとおりです
エフェクトコンビネータ
race(effects)
ミドルウェアに複数のエフェクト間でレースを実行するように指示するエフェクト記述を作成します(これはPromise.race([...])がどのように動作するかと似ています)。
effects: Object - {label: effect, ...}形式の辞書オブジェクト
例
次の例では、2つのエフェクト間のレースを実行します
- Promiseを返す関数
fetchUsersの呼び出し - Storeで最終的にディスパッチされる可能性のある
CANCEL_FETCHアクション
import { take, call, race } from `redux-saga/effects`
import fetchUsers from './path/to/fetchUsers'
function* fetchUsersSaga() {
const { response, cancel } = yield race({
response: call(fetchUsers),
cancel: take(CANCEL_FETCH)
})
}
call(fetchUsers)が最初に解決された場合、raceの結果は、{response: result}という単一のキー付きオブジェクトを持つオブジェクトになります。ここで、resultはfetchUsersの解決された結果です。
call(fetchUsers)が最初に拒否された場合、raceは拒否の理由をスローします。
fetchUsersが完了する前に、タイプCANCEL_FETCHのアクションがStoreでディスパッチされた場合、結果は単一のキー付きオブジェクト{cancel: action}になります。ここで、actionはディスパッチされたアクションです。
注釈
raceを解決するとき、ミドルウェアは自動的に負けたすべてのエフェクトをキャンセルします。
race([...effects]) (配列を使用)
race(effects)と同じですが、エフェクトの配列を渡すことができます。
例
次の例では、2つのエフェクト間のレースを実行します
- Promiseを返す関数
fetchUsersの呼び出し - Storeで最終的にディスパッチされる可能性のある
CANCEL_FETCHアクション
import { take, call, race } from `redux-saga/effects`
import fetchUsers from './path/to/fetchUsers'
function* fetchUsersSaga() {
const [response, cancel] = yield race([
call(fetchUsers),
take(CANCEL_FETCH)
])
}
call(fetchUsers)が最初に解決された場合、responseはfetchUsersの結果になり、cancelはundefinedになります。
call(fetchUsers)が最初に拒否された場合、raceは拒否の理由をスローします。
fetchUsersが完了する前に、タイプCANCEL_FETCHのアクションがStoreでディスパッチされた場合、responseはundefinedになり、cancelはディスパッチされたアクションになります。
all([...effects]) - 並列エフェクト
ミドルウェアに複数のエフェクトを並行して実行し、すべてが完了するのを待つように指示するエフェクト記述を作成します。これは、標準のPromise#allに対応するAPIです。
例
次の例では、2つのブロッキング呼び出しを並行して実行します
import { fetchCustomers, fetchProducts } from './path/to/api'
import { all, call } from `redux-saga/effects`
function* mySaga() {
const [customers, products] = yield all([
call(fetchCustomers),
call(fetchProducts)
])
}
all(effects)
all([...effects])と同じですが、race(effects)と同様に、ラベル付きのエフェクトの辞書オブジェクトを渡すことができます。
effects: Object- {label: effect, ...}形式の辞書オブジェクト
例
次の例では、2つのブロッキング呼び出しを並行して実行します
import { fetchCustomers, fetchProducts } from './path/to/api'
import { all, call } from `redux-saga/effects`
function* mySaga() {
const { customers, products } = yield all({
customers: call(fetchCustomers),
products: call(fetchProducts)
})
}
注釈
エフェクトを並行して実行すると、ミドルウェアは、次のいずれかが発生するまでジェネレータを中断します
すべてのエフェクトが正常に完了した場合:すべてのエフェクトの結果を含む配列でジェネレータを再開します。
すべてのエフェクトが完了する前に、いずれかのエフェクトが拒否された場合:ジェネレータ内で拒否エラーをスローします。
インターフェース
タスク
タスクインターフェースは、fork、middleware.run、またはrunSagaを使用してSagaを実行した結果を指定します。
| メソッド | 戻り値 |
|---|---|
| task.isRunning() | タスクがまだ返却されていないか、エラーをスローしていない場合はtrue |
| task.isCancelled() | タスクがキャンセルされた場合はtrue |
| task.result() | タスクの戻り値。タスクがまだ実行中の場合は`undefined` |
| task.error() | タスクがスローしたエラー。タスクがまだ実行中の場合は`undefined` |
| task.toPromise() | 次のいずれかのPromise
|
| task.cancel() | タスクをキャンセルします(まだ実行中の場合) |
チャネル
チャネルは、タスク間でメッセージを送受信するために使用されるオブジェクトです。送信者からのメッセージは、関心のある受信者がメッセージを要求するまでキューに入れられ、登録された受信者はメッセージが利用可能になるまでキューに入れられます。
すべてのチャネルには、バッファリング戦略(固定サイズ、ドロップ、スライディング)を定義する基盤となるバッファがあります
チャネルインターフェースは、3つのメソッドtake、put、およびcloseを定義します
Channel.take(callback): テイカーを登録するために使用されます。takeは次のルールを使用して解決されます
- チャネルにバッファリングされたメッセージがある場合、
callbackは基盤となるバッファからの次のメッセージ(buffer.take()を使用)で呼び出されます - チャネルが閉じられており、バッファリングされたメッセージがない場合、
callbackはENDで呼び出されます - それ以外の場合、
callbackはメッセージがチャネルにプットされるまでキューに入れられます
Channel.put(message): バッファにメッセージをプットするために使用されます。putは次のルールを使用して処理されます
- チャネルが閉じられている場合、putは効果がありません。
- 保留中のテイカーがある場合は、メッセージで最も古いテイカーを呼び出します。
- それ以外の場合は、基盤となるバッファにメッセージをプットします
Channel.flush(callback): チャネルからすべてのバッファリングされたメッセージを抽出するために使用されます。flushは次のルールを使用して解決されます
- チャネルが閉じられており、バッファリングされたメッセージがない場合、
callbackはENDで呼び出されます - それ以外の場合、
callbackはすべてのバッファリングされたメッセージで呼び出されます。
Channel.close(): チャネルを閉じます。つまり、これ以上のputは許可されません。すべての保留中のテイカーはENDで呼び出されます。
バッファ
チャネルのバッファリング戦略を実装するために使用されます。バッファインターフェースは、3つのメソッドisEmpty、put、およびtakeを定義します
isEmpty():バッファにメッセージがない場合はtrueを返します。チャネルは、新しいテイカーが登録されるたびにこのメソッドを呼び出しますput(message): バッファに新しいメッセージを格納するために使用されます。バッファはメッセージを格納しない場合があることに注意してください(例:ドロップバッファは指定された制限を超える新しいメッセージをドロップできます)。take(): バッファリングされたメッセージを取得するために使用されます。このメソッドの動作はisEmptyと一貫性がある必要があることに注意してください。
SagaMonitor
ミドルウェアが監視イベントをディスパッチするために使用します。実際には、ミドルウェアは6つのイベントをディスパッチします。
ルート Saga が開始されると (
runSagaまたはsagaMiddleware.runを介して)、ミドルウェアはsagaMonitor.rootSagaStartedを呼び出します。エフェクトがトリガーされると (
yield someEffectを介して)、ミドルウェアはsagaMonitor.effectTriggeredを呼び出します。エフェクトが成功で解決されると、ミドルウェアは
sagaMonitor.effectResolvedを呼び出します。エフェクトがエラーでリジェクトされると、ミドルウェアは
sagaMonitor.effectRejectedを呼び出します。エフェクトがキャンセルされると、ミドルウェアは
sagaMonitor.effectCancelledを呼び出します。最後に、Redux アクションがディスパッチされると、ミドルウェアは
sagaMonitor.actionDispatchedを呼び出します。
以下に、各メソッドの署名を示します。
sagaMonitor.rootSagaStarted(options): options は次のフィールドを持つオブジェクトです。effectId: Number - このルート Saga の実行に割り当てられた一意の IDsaga: Function - 実行を開始するジェネレータ関数args: Array - ジェネレータ関数に渡された引数
effectTriggered(options)effectId: Number - 生成されたエフェクトに割り当てられた一意の IDparentEffectId: Number - 親エフェクトの ID。raceまたはparallelエフェクトの場合、内部で生成されたすべてのエフェクトは、直接の race/parallel エフェクトを親として持ちます。トップレベルのエフェクトの場合、親は包含する Saga になります。label: String -race/allエフェクトの場合、すべての子エフェクトには、race/allに渡されたオブジェクトの対応するキーがラベルとして割り当てられます。effect: Object - 生成されたエフェクト自体
effectResolved(effectId, result)effectId: Number - 生成されたエフェクトの IDresult: any - エフェクトの正常な解決の結果。forkまたはspawnエフェクトの場合、結果はTaskオブジェクトになります。
effectRejected(effectId, error)effectId: Number - 生成されたエフェクトの IDerror: any - エフェクトのリジェクトで発生したエラー
effectCancelled(effectId)effectId: Number - 生成されたエフェクトの ID
actionDispatched(action)action: Object - ディスパッチされた Redux アクション。アクションが Saga によってディスパッチされた場合、アクションにはSAGA_ACTIONプロパティが true に設定されます (SAGA_ACTIONは@redux-saga/symbolsからインポートできます)。
外部 API
runSaga(options, saga, ...args)
Redux ミドルウェア環境の外で Saga を開始できるようにします。ストアアクション以外の外部入力/出力に Saga を接続したい場合に便利です。
runSaga は Task オブジェクトを返します。fork エフェクトから返されるものと同様です。
options: Object- 現在サポートされているオプションは次のとおりです。channel-channelのドキュメントを参照してください (ここではstdChannelを使用することをお勧めします)。dispatch(output): Function-putエフェクトを満たすために使用されます。output: any- Saga によってputエフェクトに提供される引数 (以下の注を参照)。
getState(): Function-selectおよびgetStateエフェクトを満たすために使用されます。sagaMonitor: SagaMonitor -createSagaMiddleware(options)のドキュメントを参照してください。onError: Function-createSagaMiddleware(options)のドキュメントを参照してください。context: {} -createSagaMiddleware(options)のドキュメントを参照してください。effectMiddlewares: Function[] -createSagaMiddleware(options)のドキュメントを参照してください。
saga: Function- ジェネレーター関数args: Array<any>-sagaに提供される引数
注
{channel, dispatch} は、take および put エフェクトを満たすために使用されます。これは、Saga の入出力インターフェイスを定義します。
channel は、take(PATTERN) エフェクトを満たすために使用されます。何かがチャネルに投入されるたびに、保留中の内部リスナーすべてに通知されます。Saga が take エフェクトでブロックされている場合、および take パターンが現在着信中の入力と一致する場合、Saga はその入力で再開されます。
dispatch は、put エフェクトを満たすために使用されます。Saga が yield put(output) を出力するたびに、dispatch が output で呼び出されます。
この API の使用方法の例は、こちらで確認できます。
ユーティリティ
channel([buffer])
チャネルを作成するために使用できるファクトリメソッド。オプションで、チャネルがメッセージをバッファリングする方法を制御するためのバッファを渡すことができます。
デフォルトでは、バッファが提供されない場合、チャネルは、関心のあるテーカーが登録されるまで、着信メッセージを最大 10 個までキューに入れます。デフォルトのバッファリングは、FIFO ストラテジーを使用してメッセージを配信します。新しいテーカーには、バッファ内の最も古いメッセージが配信されます。
eventChannel(subscribe, [buffer])
subscribe メソッドを使用してイベントソースをサブスクライブするチャネルを作成します。イベントソースからの着信イベントは、関心のあるテーカーが登録されるまでチャネルでキューに入れられます。
subscribe: Function基になるイベントソースをサブスクライブするために使用されます。関数は、サブスクリプションを終了するためのアンサブスクライブ関数を返す必要があります。buffer: Bufferオプションの Buffer オブジェクト。このチャネルでメッセージをバッファリングします。指定しない場合、メッセージはこのチャネルでバッファリングされません。
イベントソースが終了したことをチャネルに通知するには、提供されたサブスクライバーに END を通知できます。
例
次の例では、setInterval をサブスクライブするイベントチャネルを作成します。
const countdown = (secs) => {
return eventChannel(emitter => {
const iv = setInterval(() => {
console.log('countdown', secs)
secs -= 1
if (secs > 0) {
emitter(secs)
} else {
emitter(END)
clearInterval(iv)
console.log('countdown terminated')
}
}, 1000);
return () => {
clearInterval(iv)
console.log('countdown cancelled')
}
}
)
}
buffers
いくつかの一般的なバッファを提供します。
buffers.none(): バッファリングなし。保留中のテーカーがない場合、新しいメッセージは失われます。buffers.fixed(limit): 新しいメッセージはlimitまでバッファリングされます。オーバーフローするとエラーが発生します。limit値を省略すると、制限は 10 になります。buffers.expanding(initialSize):fixedと同様ですが、オーバーフローするとバッファが動的に拡張されます。buffers.dropping(limit):fixedと同じですが、オーバーフローするとメッセージがサイレントにドロップされます。buffers.sliding(limit):fixedと同じですが、オーバーフローすると、新しいメッセージが最後に追加され、バッファ内の最も古いメッセージがドロップされます。
cloneableGenerator(generatorFunc)
ジェネレータ関数 (function*) を受け取り、ジェネレータ関数を返します。この関数からインスタンス化されたすべてのジェネレータは複製可能になります。テスト目的のみに使用します。
例
これは、それに至るアクションを再生することなく、Saga の別のブランチをテストしたい場合に便利です。
import { cloneableGenerator } from '@redux-saga/testing-utils';
function* oddOrEven() {
// some stuff are done here
yield 1;
yield 2;
yield 3;
const userInput = yield 'enter a number';
if (userInput % 2 === 0) {
yield 'even';
} else {
yield 'odd'
}
}
test('my oddOrEven saga', assert => {
const data = {};
data.gen = cloneableGenerator(oddOrEven)();
assert.equal(
data.gen.next().value,
1,
'it should yield 1'
);
assert.equal(
data.gen.next().value,
2,
'it should yield 2'
);
assert.equal(
data.gen.next().value,
3,
'it should yield 3'
);
assert.equal(
data.gen.next().value,
'enter a number',
'it should ask for a number'
);
assert.test('even number is given', a => {
// we make a clone of the generator before giving the number;
data.clone = data.gen.clone();
a.equal(
data.gen.next(2).value,
'even',
'it should yield "even"'
);
a.equal(
data.gen.next().done,
true,
'it should be done'
);
a.end();
});
assert.test('odd number is given', a => {
a.equal(
data.clone.next(1).value,
'odd',
'it should yield "odd"'
);
a.equal(
data.clone.next().done,
true,
'it should be done'
);
a.end();
});
assert.end();
});
createMockTask()
タスクをモックするオブジェクトを返します。テスト目的のみに使用します。詳細については、タスクキャンセルに関するドキュメントを参照してください。)
チートシート
ブロッキング/非ブロッキング
| 名前 | ブロッキング |
|---|---|
| takeEvery | いいえ |
| takeLatest | いいえ |
| takeLeading | いいえ |
| throttle | いいえ |
| debounce | いいえ |
| retry | はい |
| take | はい |
| take(channel) | 場合による (API リファレンスを参照) |
| takeMaybe | はい |
| put | いいえ |
| putResolve | はい |
| put(channel, action) | いいえ |
| call | はい |
| apply | はい |
| cps | はい |
| fork | いいえ |
| spawn | いいえ |
| join | はい |
| cancel | いいえ |
| select | いいえ |
| actionChannel | いいえ |
| flush | はい |
| cancelled | はい |
| race | はい |
| delay | はい |
| all | 配列またはオブジェクトにブロッキングエフェクトがある場合はブロックします |