【问题标题】:Redux - Calling storeAPI.disapatch(action) inside middleware causes 'too much recursion'Redux - 在中间件中调用 storeAPI.disapatch(action) 会导致“递归过多”
【发布时间】:2021-06-02 15:54:04
【问题描述】:

我正在学习 Redux 基础教程。在'Writing Custom Middleware' 部分,我们了解到中间件是由一系列三个嵌套函数编写的,如下所示:

// Outer function:
function exampleMiddleware(storeAPI) {
  return function wrapDispatch(next) {
    return function handleAction(action) {
      // Do anything here: pass the action onwards with next(action),
      // or restart the pipeline with storeAPI.dispatch(action)
      // Can also use storeAPI.getState() here

      return next(action)
    }
  }
}

exampleMiddleware解释如下:

exampleMiddleware:外层函数其实就是“中间件” 本身。它会被applyMiddleware调用,接收一个storeAPI 包含商店的 {dispatch, getState} 函数的对象。这些 是相同的 dispatch 和 getState 函数,它们实际上是 商店。 如果你调用这个 dispatch 函数,它会发送 action 到中间件管道的开头。这只会被调用一次。

我不明白倒数第二句 (If you call this dispatch function, it will send the action to the start of the middleware pipeline) 是什么意思,所以我尝试在示例应用程序的 src/exampleAddons/middleware.js 提供的中间件之一中调用 store.dispatch(action) 以查看发生了什么并得到“太多递归”。 Here's the demo.

所以storeAPI.dispatch() 是所有中间件组合的组合 调度函数,而不是原始商店的dispatch,这可以解释递归。 但是storeAPI.dispatch() 有什么用呢?我是不是用错了?

applyMiddlewaresource

function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    // ...1) createStore is called and the resulting store is saved as `store`
    const store = createStore(...args)
    
    // ...2) a `dispatch` variable is defined and assigned some function
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }
    
    // ...3) a middlewareAPI object is defined containing the store's getState method and the `dispatch` function from 2).
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    
    // ...4) the middlewares passed to applyMiddleware are called with the `middlewareAPI` object from 3) and the resulting functions are saved in array `chain`.
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    
    // ...5) the middlewares are composed and the resulting "composed" middleware function is called with `store.dispatch`. 
    // This returns a composed dispatch function that chains together the `handleAction` functions of all the middlewares passed to applyMiddleware. 
    // This composed dispatch gets assigned to the `dispatch` variable from 2). 
    // Since the `storeAPI.dispatch` is referencing this variable, calling `storeAPI.dispatch` now calls the composed middleware function, which causes the infinite loop. 
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

无限循环似乎是上述注释中第 5 步重新分配的结果。但我不确定我的推理是否正确,或者我是否正确使用了storeAPI.dispatch。感谢社区在这里提供的任何指导,因为我找不到任何调用 storeAPI.dispatch() 的中间件示例。

【问题讨论】:

  • This discussion 阐明了一些用法。 storeAPI.dispatch 似乎对于有条件地在中间件中分派一个动作很有用,例如if (condition) { storeAPI.dispatch(someOtherAction) } else { next(action) } - 如果条件为真,则 someOtherAction 将通过中间件管道传递,否则当前操作将传递到下一个中​​间件。你可以看到这个模式在使用here
  • 在文档中有一个这样的例子会有所帮助。我知道storeAPI.dispatch '重新启动' 中间件链,但我不知道为什么这是一个有用的功能,直到我看到我之前评论中的链接。
  • storeAPI.dispatch(action) 将再次调度该操作,因此 redux 将运行将再次调用 storeAPI.dispatch(action) 的中间件,因此 redux 将运行将再次调用 storeAPI.dispatch(action) 的中间件,这是你的无限循环。这就像编写一个永不停止循环的递归函数:const recur = (arg)=>recur(arg)
  • 如果您查看 thunk 实现,您会发现如果操作是函数,它将不会调用 next:const thunkMiddleware = ({getState,dispatch}) => next => action => typeof action === 'function' ? action(dispatch,getState) : next(action)

标签: javascript reactjs redux react-redux


【解决方案1】:

那么 storeAPI.dispatch() 有什么用呢?

让我们检查一下thunk 中间件的简单实现。中间件基本上允许dispatch 接收一个函数(称为dispatch function)作为其参数(通常是一个普通的动作对象)。

const asyncFunctionMiddleware = store => next => action => {
    if (typeof action === 'function') {
        return action(store.dispatch, store.getState); // (*)
    }
    return next(action);
}

异步逻辑将进入 dispatch 函数,该函数提供了两个基本参数:dispatchgetState - 请参见第 (*) 行。

这两个参数足以让开发人员在其异步逻辑中与 redux 存储进行交互。对于预期的用例,开发人员的意愿是能够将操作作为正常操作(使用所有提供的中间件功能)分派。因此,这里在实现中使用了store.dispatch

如果我们改为通过next 会发生什么? 取决于 thunk 中间件在中间件​​列表中的位置,next 将是下一个包装的调度对象,然后是 thunk 中间件。 想象一下订单是a->thunk->b->store。然后在 thunk 中使用 next 进行调度将使中间件 a 对操作无效。 在 thunk 用例中不需要这种行为,因此在此处使用 store.dispatch 是合适的。

因此,thunk 是我们应该在中间件中使用store.dispatch 的情况

【讨论】:

    【解决方案2】:

    是的,在中间件中调用 storeAPI.dispatch() 会将操作发送到中间件管道的最开始处。这意味着如果我们有中间件a->b->c->store,并且b 调用storeAPI.dispatch({type: "some/action"}),中间件b 将几乎立即看到完全相同的动作对象。

    正因为如此,中间件不应该无条件调用storeAPI.dispatch(),因为这导致无限循环!这基本上与调用@987654328之类的问题相同@ 在 React 组件中无条件地 useEffect 钩子。效果在渲染后运行,setState() 将另一个渲染排队,所以如果你每次都设置状态,你总是强制重新渲染,这就是一个无限循环。这里也一样。

    因此,在中间件中对storeAPI.dispatch() 的任何使用都应包含在条件检查中,以便它仅在某些时间发生,而不是所有时间发生.

    【讨论】:

    • 谢谢@markerikson,我没有想到应该有条件地使用它。这正是它的使用方式here
    猜你喜欢
    • 2012-07-20
    • 2014-01-10
    • 2016-10-14
    • 1970-01-01
    • 1970-01-01
    • 2019-09-18
    • 1970-01-01
    • 1970-01-01
    • 2023-03-04
    相关资源
    最近更新 更多