web-dev-qa-db-ja.com

アクションがredux-sagaでディスパッチされたときにサーガをキャンセルする

ストップウォッチのタイマーを開始React STARTアクションがディスパッチされたときにコンポーネント:

import 'babel-polyfill'
import { call, put } from 'redux-saga/effects'
import { delay, takeEvery, takeLatest } from 'redux-saga'
import { tick, START, TICK, STOP } from './actions'

const ONE_SECOND = 1000

export function * timerTickWorkerSaga (getState) {
  yield call(delay, ONE_SECOND)
  yield put(tick())
}

export default function * timerTickSaga () {
  yield* takeEvery([START, TICK], timerTickWorkerSaga)
  yield* takeLatest(STOP, cancel(timerTickWorkerSaga))
}
/*
  The saga should start when either a START or a TICK is dispatched
  The saga should stop running when a stop is dispatched
*/

STOPアクションがコンポーネントからディスパッチされると、サガを停止できません。私はワーカーサガ内からcancelおよびcancelledエフェクトを使用してみました:

if(yield(take(STOP)) {
  yield cancel(timerTickWorkerSaga)
}

最初のコードブロックでのアプローチと同様に、サーガをウォッチングサービスから停止しようとします。

14
vamsiampolu

ここでいくつかのことが起こっているようです:

  1. cancel副作用 Taskオブジェクトを引数として受け取ります 。上記のコードで渡されているのは、saga/Generatorオブジェクトを作成するGeneratorFunctionだけです。ジェネレーターの概要とその仕組みについては、 この記事 をご覧ください。
  2. takeEveryおよびtakeLatestジェネレーターの前に_yield*_を使用しています。 _yield*_を使用すると、 シーケンス全体を拡散 します。だからあなたはそれをこのように考えることができます:それは行を埋めているということです

    yield* takeEvery([START, TICK], timerTickWorkerSaga)

    _while (true) {
        const action = yield take([START, TICK])
        yield fork(timeTickWorkerSaga, action)
    }
    _

    そして、私はこれがあなたのtimerTickSagaの2行目をブロックすることになると思うので、これがあなたがしようとしていることだとは思いません。代わりに、おそらく次のようにします。

    _yield fork(takeEvery, [START, TICK], timerTickWorkerSaga)
    _

    これはtakeEvery効果を分岐して、次の行をブロックしないようにします。

  3. takeLatestに渡す2番目の引数は単なるオブジェクトです CANCELエフェクトオブジェクトtakeLatestの2番目の引数は、実際にはGeneratorFunctionである必要があります。これは、STOPパターンに一致するアクションがReduxストアにディスパッチされたときに実行されます。ですから、それは本当にサガ関数であるべきです。これにより、fork(takeEvery, [START, TICK], timerTickWorkerSaga)タスクをキャンセルして、将来のSTARTおよびTICKアクションによってtimerTickWorkerSagaが実行されないようにすることができます。これは、_fork(takeEvery..._エフェクトの結果であるCANCELオブジェクトを使用してTaskエフェクトをサガに実行させることで実現できます。 Taskオブジェクトを 追加の引数 としてtakeLatestサガに追加できます。つまり、次のような結果になります。

    _export default function * timerTickSaga () {
        const workerTask = yield fork(takeEvery, [START, TICK], timerTickWorkerSaga)
        yield fork(takeLatest, STOP, cancelWorkerSaga, workerTask)
    }
    
    function* cancelWorkerSaga (task) {
        yield cancel(task)
    }
    _

詳細については、redux-sagaドキュメントの タスクキャンセルの例 をご覧ください。ここでmainサガを見ると、forkエフェクトがTaskオブジェクト/記述子を生成し、cancel生成時にさらに使用されることがわかります。 ] _効果。

12
rayd

Redux-Sagaにはこのためのメソッドがあり、race raceと呼ばれています。 2つのタスクを実行しますが、一方が完了すると、もう一方は自動的にキャンセルされます。

  • https://redux-saga.js.org/docs/advanced/RacingEffects.html

  • watchStartTickBackgroundSagaは常に実行されています

  • スタートまたはティックがあるたびに、timerTickWorkerSagaと次のSTOPアクションをリッスンするまでのレースを開始します。
  • これらのタスクの1つが完了すると、他のタスクがキャンセルされます。これがレースの動作です。
  • レース内の「タスク」と「キャンセル」の名前は重要ではなく、コードを読みやすくするためだけのものです

export function* watchStartTickBackgroundSaga() {
  yield takeEvery([START, TICK], function* (...args) {
    yield race({
      task: call(timerTickWorkerSaga, ...args),
      cancel: take(STOP)
    })
  })
}
7
Cory Danielson

Raydからの答えは非常に正しいですが、takeEveryとtakeLatestが内部でforkを実行している点で少し余分です。あなたは説明を見ることができます ここ

したがって、コードは次のようになります。

export default function* timerTickSaga() {
    const workerTask = yield takeEvery([START, TICK], timerTickWorkerSaga);
    yield takeLatest(STOP, cancelWorkerSaga, workerTask);
}

function* cancelWorkerSaga(task) {
    yield cancel(task);
}
7
Marc