在 redux saga 中监听 store 的变化

use*_*999 4 reactjs redux redux-saga

我正在尝试创建一个 redux saga 来监听状态中一个变量的变化。当它确实发生变化时,我想发送一些其他动作。这可能吗?

这就是我想要做的:

yield takeLatest(fooAction, fetchAll);

function* fetchAll() {
   const part = yield select(getPartOfState);
   if (part.flag) {
      yield call(listenToChange);
   }
}

function* listenToChange() {
   const anotherPart = yield select(getAnotherPartOfState);
   if (anotherPart === true) { // this is what I want to wait for
      // do something
   }
}
Run Code Online (Sandbox Code Playgroud)

所以我基本上想等待anotherPart改变,因为最初它会是假的,并且只在循环中执行一次(即使listenToChange被执行多次。这可能吗?

cef*_*efn 5

我采用了下面的模式,它完全符合你的描述。

它的工作原理是等待通过 store 的每个动作,并重复选择器以查看特定值是否已更改,从而触发 saga。

签名是一个包装函数,它使您能够传递一个选择器和一个 saga。传奇必须接受上一个和下一个值。对于所选值的每次更改,包装函数都会“移交”到您的 saga 一次。当满足相关条件时,您应该在您的 saga 中编写逻辑以使用正常的 yield 调用从包装生成器“接管”。

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

function* selectorChangeSaga(selector, saga) {
  let previous = yield select(selector)
  while (true) {
    const action = yield take()
    const next = yield select(selector)
    if (next !== previous) {
      yield* saga(next, previous)
      previous = next
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

下面是一个经过测试的示例,它在我的应用程序中定义了一个 saga。它生成一个正常的传奇,以正常的方式运行。

只要状态的“focusId”值发生变化,逻辑就会运行。我的传奇执行与 id 对应的远程数据的延迟加载,并随机刷新服务器列表。注意星号,尤其是yield * delegating yield!它定义了生成器如何相互“切换”。

//load row when non-null id comes into focus  
function* focusIdSaga() {
  yield* selectorChangeSaga(state => state.focusId, function* (focusId, prevFocusId) {
    const { focusType, rows } = yield select()
    if (focusType) {
      if (!prevFocusId) { //focusId previously new row (null id)
        //ensure id list is refreshed to include saved row
        yield spawn(loadIdsSaga, focusType)
      }
      if (focusId) { //newly focused row
        if (!rows[focusId]) {
          //ensure it's loaded
          yield spawn(loadRowSaga, focusType, focusId)
        }
      }
    }
  })
}
Run Code Online (Sandbox Code Playgroud)

与@alex 和@vonD 相比,我个人对监控状态感到很舒服,我觉得它表现得足够好,并提供了一种简洁可靠的方式,不会错过您关心的更改,而无需进行不必要的间接操作。如果您只跟踪操作,则很容易通过创建更改状态的操作来引入错误,同时又不记得将操作类型添加到您的过滤器中。但是,如果您认为重复选择器的性能是一个问题,您可以缩小“采取”的过滤器的范围,以便仅响应您知道会影响您正在监视的状态树部分的某些操作。

更新

基于@vonD 所示的方法,我以更简洁的方式重构了上面的示例。monitorSelector() 函数与传统的基于产量的 saga 流程交互,无需包装任何东西。它为 saga 提供了一种“阻止”以等待更改值的方法。

function* monitorSelector(selector, previousValue, takePattern = "*") {
  while (true) {
    const nextValue = yield select(selector)
    if (nextValue !== previousValue) {
      return nextValue
    }
    yield take(takePattern)
  }
}
Run Code Online (Sandbox Code Playgroud)

这是原始示例中 saga 的经过测试的版本,但针对监控状态的新方式进行了重构。

//load row when non-null id comes into focus  
function* focusIdSaga() {
  let previousFocusId
  while (true) {
    const focusId = yield* monitorSelector(state => state.focusId, previousFocusId)
    const { focusType, rows } = yield select()
    if (focusType) {
      if (!previousFocusId) { //focusId previously new row (null id)
        //ensure id list is refreshed to include saved row
        yield spawn(loadIdsSaga, focusType)
      }
      if (focusId) { //newly focused row
        if (!rows[focusId]) {
          //ensure it's loaded
          yield spawn(loadRowSaga, focusType, focusId)
        }
      }
    }
    previousFocusId = focusId
  }
}
Run Code Online (Sandbox Code Playgroud)


Von*_*onD 3

正如亚历克斯在他的评论中提到的,监听状态变化归结为监听可能触发该状态变化的操作。

take效果可以采用描述操作的各种模式作为参数,这可以帮助您做到这一点:一个操作、一组操作、一个函数等。如果您不想将此类操作列入白名单,您甚至可以不take调用一个参数(或者'*'如果你想更明确的话可以使用字符串),这让你有机会在每个操作之后检查状态。

考虑到这一点,等待一个状态具有给定值的传奇可以这样写:

function *waitForStateToHaveValue(selector, expectedValue) {
  let stateSlice = yield select(selector);
  while (stateSlice !== expectedValue) {
    yield take();
    stateSlice = yield select(selector);
  }
}
Run Code Online (Sandbox Code Playgroud)