React useReducer Hook 触发两次/如何将props传递给减速器?

IT技术 javascript reactjs react-hooks react-context
2021-04-05 01:44:17

前言/描述

我正在尝试将 React 的新钩子功能用于我正在构建的电子商务网站,并且在处理我的购物车组件中的错误时遇到了问题。

我认为以我试图通过使用多个 Context 组件来保持我的全局状态module化这一事实作为讨论的开头是相关的。我有一个单独的上下文组件用于我提供的项目类型,还有一个单独的上下文组件用于一个人的购物车中的项目。

问题

我遇到的问题是,当我调度一个动作来将一个组件添加到我的购物车时,reducer 将运行两次,就好像我已经将项目添加到我的购物车两次一样。但仅当它最初被渲染时,或出于奇怪的原因,例如将显示设置为hidden然后返回block或更改z-index以及可能的其他类似更改。

我知道这有点冗长,但这是一个相当挑剔的问题,所以我创建了两个代码笔来展示这个问题:

完整示例

最小的例子

你会看到我已经包含了一个按钮来切换display组件。这将有助于展示 css 与问题的相关性。

最后请在代码笔中监控控制台,这将显示所有按钮点击以及每个减速器的哪个部分已经运行。这些问题在完整示例中最为明显,但显示该问题的控制台语句也出现在最小示例中

问题区

我已经指出问题与我使用useContext钩子的状态来获取项目列表这一事实有关一个函数被调用来为我的useReducer钩子生成减速器,但只有在使用不同的钩子时才会出现,也就是我可以使用一个不会像钩子一样重新评估并且没有问题的函数,但我也需要来自我以前的上下文的信息,因此解决方法并不能真正解决我的问题。

相关链接

我已确定该问题不是 HTML 问题,因此我不会包含指向我尝试过的 HTML 修复程序的链接。该问题虽然由 css 触发,但并非根源于 css,因此我也不会包含 css 链接。

useReducer Action 分派两次

4个回答

正如您所指出的,原因您链接到的我相关答案相同每当Provider重新渲染时,您都会重新创建减速器,因此在某些情况下,React 将执行减速器以确定它是否需要重新渲染Provider,如果确实需要重新渲染,它将检测减速器改变了,所以 React 需要执行新的 reducer 并使用它产生的新状态,而不是之前版本的 reducer 返回的状态。

当由于对 props 或上下文或其他状态的依赖而不能将 reducer 移出函数组件时,解决方案是使用 记住您的 reducer useCallback,以便您仅在其依赖项发生变化时才创建新的 reducer(例如productsList在您的案件)。

要记住的另一件事是,您不必太担心您的减速器为一次分派执行两次。React 所做的假设是 reducer 通常足够快(它们不能做任何有副作用的事情,进行 API 调用等),因此在某些场景中需要重新执行它们的风险是值得的为了尽量避免不必要的重新渲染(如果在带有 reducer 的元素下面有一个大的元素层次结构,这可能比 reducer 昂贵得多)。

这是Providerusing的修改版本useCallback

const Context = React.createContext();
const Provider = props => {
  const memoizedReducer = React.useCallback(createReducer(productsList), [productsList])
  const [state, dispatch] = React.useReducer(memoizedReducer, []);

  return (
    <Context.Provider value={{ state, dispatch }}>
      {props.children}
    </Context.Provider>
  );
}

这是您的 codepen 的修改版本:https ://codepen.io/anon/pen/xBdVMp ? editors = 0011

useCallback如果您不熟悉如何使用此钩子,这里有几个与此相关的答案可能会有所帮助:

@Ryan 还没来得及完全阅读你的这个和其他答案,但是 AFAIK,这不应该导致错误吗?(如果您的减速器是纯的,并且没有副作用)。因为如果每个增量操作都调用了两次 reducer,那么第二次调用仍然会像第一次调用一样获得状态。你可能想更好地强调这一点,但你确实暗示了这一点。
2021-05-31 01:44:17
@gmoniava 正确。被调用两次的减速器应该是无害的。
2021-06-11 01:44:17
哇这个答案是金色的,非常感谢!
2021-06-20 01:44:17

将 Reducer 与帮助我解决问题的功能组件分开

'分离减速器'是什么意思?我收到错误消息“无法在顶层调用 React Hook“useReducer”。尝试分离减速器时?
2021-06-10 01:44:17

一个基于 Ryans 优秀答案的例子。

  const memoizedReducer = React.useCallback((state, action) => {
    switch (action.type) {
      case "addRow":
        return [...state, 1];
      case "deleteRow":
        return [];
      default:
        throw new Error();
    }
  }, []) // <--- if you have vars/deps inside the reducer that changes, they need to go here

  const [data, dispatch] = React.useReducer(memoizedReducer, _data);

当我阅读一些useContext源代码时,我发现

const useContext = hook(class extends Hook {
  call() {
    if(!this._ranEffect) {
      this._ranEffect = true;
      if(this._unsubscribe) this._unsubscribe();
      this._subscribe(this.Context);
      this.el.update();
    }
  }

第一次更新effect后,更新后会调用一个like。value订阅正确的上下文后,例如,解析来自 的值Provider,它请求另一个更新。由于_ranEffect标志,这不是循环

在我看来,如果上述情况成立React,渲染引擎将被调用两次。