我采用了下面的模式,它完全符合您的描述。
它的工作原理是等待通过 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
}
}
}
下面是一个经过测试的示例,它在我的应用程序中定义了一个 saga。它生成一个正常的 saga,以正常的方式运行。
只要状态的“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 相比,我个人对监控状态感到满意,我觉得它执行得很好,并提供了一种简洁可靠的方法,不会错过您关心的更改,而无需不必要的间接。如果您只跟踪操作,很容易通过创建更改状态的操作来引入错误,而不记得将操作类型添加到您的过滤器。但是,如果您认为重复选择器的性能是一个问题,您可以缩小“采取”的过滤器,以便仅响应您知道会影响您正在监视的状态树部分的某些操作。
更新
在@vonD 展示的方法的基础上,我以一种更简洁的方式重构了上面的示例。 monitorSelector() 函数与传统的基于产量的 saga 流进行交互,而无需包装任何内容。它为 saga 提供了一种“阻塞”以等待更改值的方法。
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
}
}