foreword/description
我正在尝试将React的新挂钩功能用于我正在构建的电子商务网站,并且在我的购物车组件中遇到一个错误的问题。
我认为,我试图通过使用多个上下文组件来保持我的全球状态模块化是重要的。我有一个单独的上下文组件,用于我提供的项目类型,以及一个人购物车中这些项目的单独上下文组件。
问题
我遇到的问题是,当我派遣一项操作将组件添加到购物车中时,还原器将运行两次,好像我已经将物品添加到了购物车中两次。但是,只有在最初渲染或出于怪异的原因(例如显示)设置为hidden
,然后返回到block
或进行z-index
的更改以及可能其他类似的更改时。
我知道这有点冗长,但它是针织挑剔的问题,所以我创建了两个展示问题的代码:
完整示例
最低示例
您会看到我包含了一个按钮来切换组件的display
。这将有助于展示CSS与问题的相关性。
最后,请监视代码笔中的控制台,这将显示所有按钮点击以及每个还原器的哪一部分已运行。在完整示例中,这些问题最为明显,但是控制台语句显示该问题也存在于最低示例中。
。问题区域
我已经指出了与我使用useContext
钩的状态获取项目列表的事实有关的问题。调用一个函数来生成我的useReducer
钩的还原器,但是只有在使用其他钩子又名时才会出现,我可以使用一个不会像钩子那样重新评估的函数,但是我也没有问题,但是我也是需要我以前上下文中的信息,以使解决方案并不能真正解决我的问题。
相关链接
我已经确定问题不是HTML问题,因此我不会包括我尝试过的HTML修复程序的链接。该问题虽然由CSS触发,但并未植根于CSS,因此我也不会包括CSS链接。
用户设定的操作两次派遣
正如您所指出的,原因与您链接的相关答案相同。每当重新渲染Provider
时,您都在重新创建还原器,因此在某些情况下,React会执行还原器,以确定它是否需要重新渲染Provider
,并且如果需要重新渲染,则它将检测到该检测更改还原器,因此React需要执行新的还原器并使用由其生成的新状态,而不是先前版本的还原器返回的状态。
由于对道具,上下文或其他状态的依赖性,当您不能仅将还原器移出功能组件时,该解决方案是使用useCallback
记住还原器,以便只有在其依赖关系更改时创建一个新的还原器(例如productsList
在您的情况下)。
要记住的另一件事是,您不必太担心还原器两次执行单个调度。假设的做法是,还原器通常会足够快(他们对副作用,拨打api呼叫等无能为力),值得在某些情况下重新执行它们的风险为了避免不必要的重新租户(如果使用还原器下方存在较大的元素层次结构,它可能比还原贵得多)。
这是使用useCallback
的Provider
的修改版本:
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/pen/xbdvmp?editors=0011
以下是与useCallback
相关的几个答案,如果您不熟悉如何使用此钩子,可能会有所帮助:
- 麻烦的简单示例uce钩usecallback
- React Hook usecallback会导致孩子重新渲染
将还原器与帮助我求解的功能组件分开基于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
。例如,在value
订阅正确的上下文之后,例如,从Provider
中解析值,它请求另一个更新。这不是循环,这要归功于_ranEffect
标志。
在我看来,如果以上是React
,则渲染引擎被称为两次。
您是否在应用中使用strictmode?
https://beta.reactjs.org/reference/react/strictmode
根据文档:
&quot;严格的模式可启用额外的开发检查组件内部的整个组件树。这些检查可以帮助您在开发过程的早期查找组件中的常见错误。"
&quot;尽管严格的模式检查仅在开发中运行,但它们可以帮助您找到代码中已经存在的错误,但在生产中可靠地重现可能很棘手。严格的模式使您可以在用户报告它们之前修复错误。
&quot;严格模式可以在开发中进行以下检查:
您的组件将重新渲染额外的时间来查找不纯渲染引起的错误。
您的组件将重新运行效果一个额外的时间来查找缺失效果清理引起的错误。
您的组件将被检查是否使用已弃用的API。
所有这些检查都是仅开发的,不会影响生产构建。&quot
' React假定您写的每个组件都是纯函数。这意味着您编写的React组件必须始终返回相同的JSX给定相同的输入(Prop,State和Context)。
违反此规则的组件表现得不可预测并引起错误。为了帮助您找到意外的不纯正代码,严格的模式在开发中调用了您的某些功能(只有纯粹的功能)。这包括:
-
您的组件功能主体(仅上级逻辑,所以这不包括事件内部的代码)
-
您传递给USESESTATE,SET功能,USEMEMO或用户EdeDucer 的功能某些类组件方法,例如构造函数,渲染,shordComponentUpdate(请参阅整个列表)
-
如果功能是纯粹的,则运行两次不会改变其行为,因为纯函数每次都会产生相同的结果。
希望这对您有帮助!