前言/说明
我正在尝试将React的新钩子功能用于我正在构建的电子商务网站,并且一直在解决我的购物车组件中的错误问题。
我认为与讨论前言有关,我试图通过使用多个Context组件来保持我的全局状态模块化。我有一个单独的上下文组件,用于我提供的项目类型,以及一个单独的上下文组件,用于个人购物车中的项目。
问题
我遇到的问题是,当我发送一个操作来将一个组件添加到我的购物车时,减速器将运行两次,就像我已经将项目添加到我的购物车两次一样。但只有在最初渲染时,或者出于奇怪的原因,例如将显示设置为hidden
然后返回block
或更改z-index
以及可能的其他类似更改。
我知道这有点冗长,但它是相当针织的挑剔问题所以我创建了两个代码来展示这个问题:
您将看到我已经包含一个按钮来切换组件的display
。这将有助于展示css与问题的相关性。
最后请在代码栏中监控控制台,这将显示所有按钮单击以及每个减速器的哪个部分已运行。这些问题在full example中最明显,但是控制台语句显示问题也出现在minimum example中。
问题区域
我已经确定问题与我使用useContext
钩子的状态得到物品清单这一事实有关。调用一个函数来为我的useReducer
钩子生成reducer,但只有在使用不同的钩子时才会出现。我可以使用一个不会像钩子那样重新评估的函数而且没有问题,但我也是需要我之前的Context中的信息,以便解决方法不能解决我的问题。
相关链接
我已经确定问题不是HTML问题所以我不会包含我尝试过的HTML修补程序的链接。这个问题虽然由css触发,但不是以css为根,所以我也不会包含css链接。
正如您所指出的,原因与您所链接的related answer相同。每当Provider
被重新渲染时,你就会重新创建你的reducer,所以在某些情况下React会执行reducer以确定它是否需要重新渲染Provider
,如果它确实需要重新渲染它会检测到reducer被更改,因此React需要执行新的reducer并使用它生成的新状态,而不是先前版本的reducer返回的状态。
当你不能仅仅因为依赖于道具或上下文或其他状态而将reducer从你的函数组件中移出时,解决方案是使用useCallback
来记忆你的reducer,这样你只能在它的依赖变化时创建一个新的reducer(例如productsList
)在你的情况下)。
另外要记住的是,您不必过于担心减速器执行两次单次调度。 React的假设是减速器通常足够快(它们不能做任何副作用,进行API调用等),在某些情况下需要重新执行它们的风险是值得的。为了避免不必要的重新渲染(如果在带有reducer的元素下面有一个大的元素层次结构,这可能比reducer更昂贵)。
这是使用Provider
的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
相关的几个答案,如果您不熟悉如何使用此钩子,可能会有所帮助: