前言/说明
我正在尝试将React的新挂钩函数用于我正在构建的电子商务网站,但是在解决购物车组件中的错误时遇到了问题。
我认为在讨论之前进行讨论很重要,因为我试图通过使用多个上下文组件来保持全局状态模块化。对于我提供的商品类型,我有一个单独的上下文组件,对于一个人的购物车中的商品,我有一个单独的上下文组件。
问题
我遇到的问题是,当我调度将零件添加到购物车的操作时,减速器将运行两次,就像我两次将零件添加到购物车一样。但是仅当它最初被渲染时,或者由于诸如显示之类的奇怪原因而被设置为hidden
,然后又回到block
或z-index
中的更改以及可能的其他类似更改。
我知道这很冗长,但这是一个很挑剔的问题,因此我创建了两个代码库来展示该问题:
您将看到我包含一个用于切换组件display
的按钮。这将有助于展示CSS与问题的相关性。
最后,请用代码笔监视控制台,这将显示所有按钮单击以及每个reducer的哪一部分已运行。该问题在full example中最明显,但是控制台语句显示该问题也在minimum example中出现。
问题区域
我已查明问题与以下事实有关:我正在使用useContext
挂钩的状态来获取项目列表。调用了一个函数来为我的useReducer
钩子生成化简器,但是仅当使用了另一个钩子时才出现,也就是我可以使用像钩子一样不会重新评估的函数,并且没有问题,但我还需要以前的上下文中的信息,以便解决方法不能真正解决我的问题。
相关链接
我已确定问题不是HTML问题,因此我将不包含我尝试过的HTML修复程序的链接。这个问题虽然是由CSS触发的,但并非植根于CSS,所以我也不会包含CSS链接。
答案 0 :(得分:9)
正如您所指出的,原因与您链接到的我的related answer相同。每当重新渲染Provider
时,您都在重新创建化简器,因此在某些情况下,React将执行化简器以确定是否需要重新渲染Provider
以及是否确实需要要重新渲染,它将检测到Reducer已更改,因此React需要执行新的Reducer并使用其产生的新状态,而不是Reducer先前版本返回的状态。
当由于依赖props或上下文或其他状态而不能仅将reducer移出功能组件时,解决方案是使用useCallback
来记住您的reducer,以便仅创建一个新的reducer当其依存关系发生变化时(例如您的情况下为productsList
)。
要记住的另一件事是,您不必为减速器为一次调度执行两次而过分担心。 React所做的假设是,Reducer通常会足够快(它们无法执行任何副作用,进行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
相关的几个答案,如果您不熟悉如何使用此挂钩,可能会有所帮助:
答案 1 :(得分:2)
将Reducer与帮助我解决矿井的功能部件分开
答案 2 :(得分:0)
基于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);
答案 3 :(得分:0)
当我阅读一些 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
的上述情况成立,渲染引擎会被调用两次。