React中useEffect更新Context触发无限循环的问题解决
解决React组件无限重渲染(Maximum update depth exceeded)问题
问题场景
开发图表绘制库时,需要实现子组件Curve的props更新时,同步更新CurveTrack中的Context状态,但在Curve组件的useEffect中调用setAxis时触发了无限重渲染错误:
Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render
相关代码
CurveTrack组件
const CurveTrack: FC<CurveTrackProps> = ({ children, className, ...props }) => { const [axes, setAxes] = useState<{ [key: string]: Axis }>({}); const setAxis = useCallback((axis: Axis) => { setAxes((oldAxes) => ({ ...oldAxes, [axis.name]: axis })); }, []); const deleteAxis = (axis: Axis) => { const updatedAxes = { ...axes }; delete updatedAxes[axis.name]; setAxes(updatedAxes); }; return ( <CurveTrackContext.Provider value={{ axes, setAxis, deleteAxis }}> <VictoryChart width={1500} height={400}> {Object.values(axes).map((axis) => ( <VictoryAxis key={axis.name} domain={axis.domain} style={{ axis: { stroke: axis.color } }} /> ))} {children} </VictoryChart> </CurveTrackContext.Provider> ); };
Curve组件
const Curve: FC<any> = ({ className, children, ...props }) => { const { depth, range: xRange } = useLogView(); const { setAxis } = useAxes(); const curve = { ...defaultCurve, ...props }; const curveData = createChart(depth, curve.data); const rangedData = rangeData(curveData, xRange); const defaultRange = getRange(rangedData.map((data) => data.y)); const yRange: Range = { ...defaultRange, ...curve.scope }; useEffect(() => { const domain: ForAxes<DomainTuple> = [yRange.min, yRange.max]; const axis = { name: curve.name, color: curve.style.color, domain }; setAxis(axis); }, [curve, yRange]); return <VictoryLine {...props} data={rangedData} style={{ data: { stroke: curve.style.color } }} />; };
问题根源分析
Curve组件中对象依赖的不稳定性:
const curve = { ...defaultCurve, ...props }每次组件渲染都会创建新对象,导致useEffect的依赖curve每次引用都不同,触发useEffect重复执行。yRange同理,每次渲染都是新创建的对象,同样会触发依赖变化。- 每次
useEffect执行都会调用setAxis更新Context,导致CurveTrack重新渲染,进而引发Curve组件再次渲染,形成循环。
CurveTrack组件中Context值的不稳定:
deleteAxis函数未用useCallback包裹,每次CurveTrack渲染时都会生成新的函数引用,导致Provider的value对象变化,触发所有消费Context的子组件重新渲染,加重循环问题。
解决方案
1. 稳定Curve组件的依赖项
用useMemo缓存需要复用的对象,避免每次渲染创建新引用;同时拆解对象依赖为基本类型,确保依赖比较是值而非引用:
const Curve: FC<any> = ({ className, children, ...props }) => { const { depth, range: xRange } = useLogView(); const { setAxis } = useAxes(); // 缓存curve对象,仅在props变化时重新创建 const curve = useMemo(() => ({ ...defaultCurve, ...props }), [props]); const curveData = createChart(depth, curve.data); const rangedData = rangeData(curveData, xRange); // 缓存yRange,仅在依赖变化时重新计算 const yRange: Range = useMemo(() => { const defaultRange = getRange(rangedData.map((data) => data.y)); return { ...defaultRange, ...curve.scope }; }, [curve.scope, rangedData]); useEffect(() => { const domain: ForAxes<DomainTuple> = [yRange.min, yRange.max]; const axis = { name: curve.name, color: curve.style.color, domain }; setAxis(axis); // 依赖改为基本类型,避免引用变化触发重复执行 }, [curve.name, curve.style.color, yRange.min, yRange.max, setAxis]); return <VictoryLine {...props} data={rangedData} style={{ data: { stroke: curve.style.color } }} />; };
2. 稳定CurveTrack组件的Context值
给deleteAxis添加useCallback并使用函数式更新,同时用useMemo缓存Context的value:
const CurveTrack: FC<CurveTrackProps> = ({ children, className, ...props }) => { const [axes, setAxes] = useState<{ [key: string]: Axis }>({}); const setAxis = useCallback((axis: Axis) => { setAxes((oldAxes) => ({ ...oldAxes, [axis.name]: axis })); }, []); // 用useCallback稳定deleteAxis引用,使用函数式更新避免依赖axes const deleteAxis = useCallback((axis: Axis) => { setAxes(oldAxes => { const updatedAxes = { ...oldAxes }; delete updatedAxes[axis.name]; return updatedAxes; }); }, []); // 缓存Context值,仅当依赖变化时更新 const contextValue = useMemo(() => ({ axes, setAxis, deleteAxis }), [axes, setAxis, deleteAxis]); return ( <CurveTrackContext.Provider value={contextValue}> <VictoryChart width={1500} height={400}> {Object.values(axes).map((axis) => ( <VictoryAxis key={axis.name} domain={axis.domain} style={{ axis: { stroke: axis.color } }} /> ))} {children} </VictoryChart> </CurveTrackContext.Provider> ); };
3. 补充检查
开启ESLint的react-hooks/exhaustive-deps规则,确保useEffect和useCallback的依赖数组完整且无冗余,避免遗漏或错误的依赖导致的问题。
内容的提问来源于stack exchange,提问作者Ann Voronova
相关产品推荐
相关产品推荐

