React useReducer Hook 触发两次/如何将道具传递给减速器? [英] React useReducer Hook fires twice / how to pass props to reducer?

查看:30
本文介绍了React useReducer Hook 触发两次/如何将道具传递给减速器?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

前言/描述

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

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

问题

我遇到的问题是,当我分派一个动作将一个组件添加到我的购物车时,reducer 会运行两次,就好像我已经将项目添加到我的购物车两次一样.但只有当它最初被渲染时,或出于奇怪的原因,例如显示设置为 hidden 然后返回到 blockz-index 和其他可能的类似变化.

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

完整示例

最小示例

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

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

问题区域

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

相关链接

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

useReducer Action 被分派两次

解决方案

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

当由于依赖于 props 或上下文或其他状态而不能将 reducer 移出函数组件时,解决方案是使用 useCallback,这样你只在依赖关系改变时才创建一个新的reducer(例如productsList在你的情况下).

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

这是使用 useCallbackProvider 的修改版本:

const Context = React.createContext();const 提供者 = 道具 =>{const memoizedReducer = React.useCallback(createReducer(productsList), [productsList])const [state, dispatch] = React.useReducer(memoizedReducer, []);返回 (<Context.Provider value={{ state, dispatch }}>{props.children}</Context.Provider>);}

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

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

FOREWORD / DESCRIPTION

I am trying to use React's new hooks feature for an e-commerce website that I am building, and have been having an issue working a bug out of my shopping cart component.

I think it is relevant to preface the discussion with the fact that I am trying to keep my global state modular by using multiple Context components. I have a separate context component for the types of items that I offer, and a separate context component for the items in a person's shopping cart.

PROBLEM

The issue I am having is that when I dispatch an action to add a component to my cart, the reducer will run twice as if I had added the item to my cart twice. But only when it is initially rendered, or for weird reasons such as the display is set to hidden and then back to block or for a change in the z-index and potentially other similar changes.

I know this is kind of verbose, but it is rather knit picky issue so I have created two codepens that showcase the issue:

full example

minimum example

You will see that I have included a button to toggle the display of the components. This will help showcase the correlation of the css to the issue.

Finally please monitor the console in the code pens, this will show all button clicks and which part of each reducer has been run. The issues are most evident in the full example, but the console statements display the issue is also present in the minimum example.

PROBLEM AREA

I have pinpointed the problem to be related to the fact that I am using the state of a useContext hook to get the items list. A function is called to generate the reducer for my useReducer hook, but only arises when a different hook is used AKA I could use a function that wouldn't be subject to re-eval like hook is and not have the issue, but I also need the info from my previous Context so that workaround doesn't really fix my issue.

Relevant Links

I have determined the issue is NOT an HTML issue so I will not include the links to the HTML fixes I have tried. The issue, while triggered by css, is not rooted in css so I will not include css links either.

useReducer Action dispatched twice

解决方案

As you indicated, the cause is the same as the related answer of mine that you linked to. You are re-creating your reducer whenever Provider is re-rendered, so in some cases React will execute the reducer in order to determine whether or not it needs to re-render Provider and if it does need to re-render it will detect that the reducer is changed, so React needs to execute the new reducer and use the new state produced by it rather than what was returned by the previous version of the reducer.

When you can't just move the reducer out of your function component due to dependencies on props or context or other state, the solution is to memoize your reducer using useCallback, so that you only create a new reducer when its dependencies change (e.g. productsList in your case).

The other thing to keep in mind is that you shouldn't worry too much about your reducer executing twice for a single dispatch. The assumption React is making is that reducers are generally going to be fast enough (they can't do anything with side effects, make API calls, etc.) that it is worth the risk of needing to re-execute them in certain scenarios in order to try to avoid unnecessary re-renders (which could be much more expensive than the reducer if there is a large element hierarchy underneath the element with the reducer).

Here's a modified version of Provider using 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>
  );
}

Here is a modified version of your codepen: https://codepen.io/anon/pen/xBdVMp?editors=0011

Here are a couple answers related to useCallback that might be helpful if you aren't familiar with how to use this hook:

这篇关于React useReducer Hook 触发两次/如何将道具传递给减速器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆