多次调用 sagaMiddleware.run 是否安全?

Is it safe to call sagaMiddleware.run multiple times?

我在应用程序中使用 redux 和 redux-saga 来管理状态和异步操作。为了让我的生活更轻松,我写了一个 class 本质上充当 saga 管理器,使用一种 "registers" saga 的方法。这个 register 方法派生出新的 saga,并使用 redux-saga/effects/all:

将它与所有其他已注册的 saga 结合起来
class SagasManager {
    public registerSaga = (saga: any) => {
        this._sagas.push(fork(saga));
        this._combined = all(this._sagas);
    }
}

然后我的商店使用这个 class 来获取 _combined saga,据说在所有 sagas 都注册之后:

const store = Redux.createStore(
    reducer, 
    initialState, 
    compose(Redux.applyMiddleware(sagaMiddleware, otherMiddleware)),
);
sagaMiddleware.run(sagasManager.getSaga());

但是,我 运行 遇到了一个问题,即根据情况(如进口订单),这并不总是按预期工作。发生的事情是一些传奇在调用 sagaMiddleware.run.

之前没有被注册

我通过在 SagasManager:

上提供回调来解决这个问题
class SagasManager {
    public registerSaga = (saga: any) => {
        this._sagas.push(fork(saga));
        this._combined = all(this._sagas);
        this.onSagaRegister();
    }
}

然后商店代码可以用作

sagasManager.onSagaRegister = () => sagaMiddleware.run(sagasManager.getSaga());

这似乎可行,但我无法在文档中找到这是否安全。我确实看到 .run returns a Task,它有取消等方法,但是因为我的问题只是在构建商店和应用程序之间的尴尬时间渲染我不认为那会是一个问题。

谁能解释这是否安全,如果不安全,更好的解决方案是什么?

这可能取决于您所说的 "safe" 是什么意思。在这种情况下,你的意思到底是什么?

首先,这是 source of runSaga itself,以及 saga 中间件使用它的地方。

往里看 runSaga,我看到:

export function runSaga(options, saga, ...args) {
  const iterator = saga(...args)

  // skip a bunch of code

  const env = {
    stdChannel: channel,
    dispatch: wrapSagaDispatch(dispatch),
    getState,
    sagaMonitor,
    logError,
    onError,
    finalizeRunEffect,
  }

  const task = proc(env, iterator, context, effectId, getMetaInfo(saga), null)

  if (sagaMonitor) {
    sagaMonitor.effectResolved(effectId, task)
  }

  return task
}

我从中得出的结论是,当您调用 runSaga(mySagaFunction) 时,"destructive" 不会发生任何事情。但是,如果您多次使用相同的 saga 函数调用 runSaga(),您可能会拥有该 saga 运行 的多个副本,这可能会导致您的应用程序不希望出现的行为。

您可能想尝试一下。例如,如果您有一个计数器应用并执行此操作,会发生什么情况?

function* doIncrement() {
    yield take("DO_INCREMENT");
    put({type : "INCREMENT"});
}

sagaMiddleware.runSaga(doIncrement);
sagaMiddleware.runSaga(doIncrement);

store.dispatch({type : "DO_INCREMENT"});

console.log(store.getState().counter);
// what's the value?

我猜计数器是 2,因为 doIncrement 的两个副本都会响应。

如果您担心此类行为,那么您可能需要确保取消之前的 sagas。

我实际上 运行 不久前浏览了一个在热重载期间取消 sagas 的方法,并且 included a version of that in a gist for my own usage。您可能想参考它以获取想法。