【问题标题】:React useReducer Hook fires twice / how to pass props to reducer?React useReducer Hook 触发两次/如何将道具传递给减速器?
【发布时间】:2019-07-30 01:09:12
【问题描述】:

前言/描述

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

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

问题

我遇到的问题是,当我发送一个操作以将一个组件添加到我的购物车时,reducer 将运行两次,就好像我将商品添加到我的购物车两次一样。但仅在最初渲染时,或出于奇怪的原因,例如显示设置为 hidden,然后返回到 block,或更改 z-index 并可能进行其他类似更改。

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

full example

minimum example

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

最后请监控代码笔中的控制台,这将显示所有按钮单击以及每个减速器的哪个部分已运行。这些问题在full example 中最为明显,但控制台语句显示该问题也存在于minimum example 中。

问题领域

我已经确定问题与我使用useContext 钩子的状态来获取项目列表有关。调用了一个函数来为我的 useReducer 钩子生成减速器,但只有在使用不同的钩子时才会出现 AKA 我可以使用一个不会像钩子一样重新评估的函数并且没有问题,但是我还需要我以前的 Context 中的信息,这样解决方法并不能真正解决我的问题。

相关链接

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

useReducer Action dispatched twice

【问题讨论】:

    标签: javascript reactjs react-hooks react-context


    【解决方案1】:

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

    当由于依赖于 props 或 context 或其他状态而无法将 reducer 移出函数组件时,解决方案是使用 useCallback 来记忆你的 reducer,这样你就只在它的时候创建一个新的 reducer依赖项更改(例如 productsList 在您的情况下)。

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

    这是使用useCallbackProvider 的修改版本:

    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 两次,那么第二次调用仍然会像第一次调用一样获得状态。您可能想更好地强调这一点,但您确实暗示了这一点。
    • @gmoniava 正确。被调用两次的 reducer 应该是无害的。
    【解决方案2】:

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

    【讨论】:

    • 'seperate the reducer' 是什么意思?我收到一个错误“无法在顶层调用 React Hook “useReducer”。尝试分离减速器时?
    【解决方案3】:

    基于 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);
    
    

    【讨论】:

      【解决方案4】:

      当我阅读一些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。在value 订阅正确的上下文之后,例如,从Provider 解析值,它会请求另一个 更新。这不是循环,感谢_ranEffect 标志。

      在我看来,如果 React 以上是真的,渲染引擎会被调用两次。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-07-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-08-01
        • 2019-11-04
        • 2019-05-01
        • 1970-01-01
        相关资源
        最近更新 更多