メインコンテンツにスキップ

将来のアクションのプル

これまで、私たちは受信したアクションごとに新しいタスクを生成するためにヘルパーエフェクトtakeEveryを使用してきました。これは、redux-thunkの動作をいくらか模倣しています。たとえば、コンポーネントがfetchProductsアクションクリエイターを呼び出すたびに、アクションクリエイターは制御フローを実行するためにthunkをディスパッチします。

実際には、takeEveryは、より低レベルで強力なAPIの上に構築された内部ヘルパー関数の単なるラッパーエフェクトです。このセクションでは、アクションの監視プロセスを完全に制御できるようにすることで、複雑な制御フローを構築できる新しいエフェクトtakeを紹介します。

基本的なロガー

ストアにディスパッチされたすべてのアクションを監視し、それらをコンソールにログを記録するSagaの基本的な例を見てみましょう。

takeEvery('*')(ワイルドカード*パターンを使用)を使用すると、タイプに関係なく、ディスパッチされたすべてのアクションをキャッチできます。

import { select, takeEvery } from 'redux-saga/effects'

function* watchAndLog() {
yield takeEvery('*', function* logger(action) {
const state = yield select()

console.log('action', action)
console.log('state after', state)
})
}

次に、takeエフェクトを使用して上記と同じフローを実装する方法を見てみましょう。

import { select, take } from 'redux-saga/effects'

function* watchAndLog() {
while (true) {
const action = yield take('*')
const state = yield select()

console.log('action', action)
console.log('state after', state)
}
}

takeは、以前見たcallおよびputとよく似ています。これは、ミドルウェアに特定のアクションを待つように指示する別のコマンドオブジェクトを作成します。callエフェクトの結果の動作は、Promiseが解決されるまでミドルウェアがジェネレーターを中断する場合と同じです。takeの場合、一致するアクションがディスパッチされるまでジェネレーターは中断されます。上記の例では、アクションがディスパッチされるまでwatchAndLogは中断されます。

無限ループwhile (true)を実行していることに注意してください。これは、完了まで実行する動作がないジェネレーター関数であることを思い出してください。私たちのジェネレーターは、アクションが発生するのを待って、各反復でブロックします。

takeを使用すると、コードの記述方法に微妙な影響があります。takeEveryの場合、呼び出されたタスクは、いつ呼び出されるかを制御できません。一致するアクションごとに何度も何度も呼び出されます。また、監視を停止するタイミングも制御できません。

takeの場合、制御は反転されます。アクションがハンドラータスクにプッシュされるのではなく、Saga自体がアクションをプルしています。Sagaが通常関数呼び出しaction = getNextAction()を実行しているかのように見えます。この関数はアクションがディスパッチされたときに解決されます。

この制御の反転により、従来のアプローチであるプッシュでは実現が難しい制御フローを実装できます。

基本的な例として、Todoアプリケーションで、ユーザーアクションを監視し、ユーザーが最初の3つのTodoを作成した後に、おめでとうメッセージを表示したいとします。

import { take, put } from 'redux-saga/effects'

function* watchFirstThreeTodosCreation() {
for (let i = 0; i < 3; i++) {
const action = yield take('TODO_CREATED')
}
yield put({type: 'SHOW_CONGRATULATION'})
}

while (true)の代わりに、3回だけ反復するforループを実行しています。最初の3つのTODO_CREATEDアクションを取得した後、watchFirstThreeTodosCreationはアプリケーションにおめでとうメッセージを表示させてから終了させます。これは、ジェネレーターがガベージコレクションされ、これ以上の監視が行われないことを意味します。

プルアプローチのもう1つの利点は、使い慣れた同期スタイルを使用して制御フローを記述できることです。たとえば、LOGINLOGOUTの2つのアクションでログインフローを実装するとします。takeEvery(またはredux-thunk)を使用すると、LOGIN用とLOGOUT用の2つの別々のタスク(またはthunk)を記述する必要があります。

その結果、ロジックが2つの場所に分散されます。私たちのコードを読んでいる人がそれを理解するためには、2つのハンドラーのソースを読み、両方のロジック間のリンクを頭の中で作成する必要があります。つまり、コードのさまざまな場所に配置されたロジックを頭の中で正しく並べ替えて、フローのモデルを頭の中で再構築する必要があるということです。

プルモデルを使用すると、同じアクションを繰り返し処理する代わりに、同じ場所にフローを記述できます。

function* loginFlow() {
while (true) {
yield take('LOGIN')
// ... perform the login logic
yield take('LOGOUT')
// ... perform the logout logic
}
}

loginFlow Sagaは、予期されるアクションシーケンスをより明確に伝えています。LOGINアクションの後に必ずLOGOUTアクションが続き、LOGOUTの後に必ずLOGINが続くことを認識しています(優れたUIは、予期しないアクションを非表示または無効にすることにより、アクションの一貫した順序を常に強制する必要があります)。