在redux传奇中聆听商店的变化

时间:2017-03-28 14:50:41

标签: 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
   }
}

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

2 个答案:

答案 0 :(得分:1)

正如亚历克斯在评论中提到的那样,倾听状态变化归结为听取可能引发这一状态改变的行为。

take效果可以采用各种模式描述操作作为参数,这可以帮助您做到这一点:动作,动作数组,函数等。如果您不想白名单这样的操作,您甚至可以在没有参数的情况下调用take(如果您想要更明确,则使用字符串'*'),这使您有机会在每次操作后检查状态。

有了这个想法,等待一个状态具有给定值的传奇可以这样写:

function *waitForStateToHaveValue(selector, expectedValue) {
  let stateSlice = yield select(selector);
  while (stateSlice !== expectedValue) {
    yield take();
    stateSlice = yield select(selector);
  }
}

答案 1 :(得分:0)

我采用了以下模式,该模式正是您所描述的。

它通过等待通过商店的每个动作来工作,并重复选择器以查看特定值是否已更改,从而触发传奇。

签名是一个包装函数,使您可以传递选择器和传奇。传奇必须接受上一个和下一个值。对于所选值的每次更改,包装函数都会“移交给”您的传奇。您应该在传奇中编写逻辑,以便在满足相关条件时使用正常的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
    }
  }
}

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

只要状态的“ focusId”值发生更改,逻辑就会运行。我的sagas进行与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)
        }
      }
    }
  })
}

通过与@alex和@vonD进行对比,我个人很舒服地监视状态,并且我觉得它可以充分发挥作用,并且提供了一种简洁而可靠的方式,可以在没有不必要的间接访问的情况下不丢失您关心的更改。如果仅跟踪动作,则很容易通过创建可更改状态的动作来引入错误,而不必记住将动作类型添加到过滤器中。但是,如果您认为重复选择器的性能是一个问题,则可以缩小“采取”的过滤条件,以便仅响应您知道会影响要监视的状态树部分的某些操作。

UPDATE

以@vonD所示的方法为基础,我以一种更简洁的方式重构了上面的示例。 monitorSelector()函数与常规的基于收益的传奇故事流进行交互,而无需包装任何内容。它为传奇提供了一种“阻止”等待值更改的方式。

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

这是原始示例中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
  }
}