You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 } }} />;
};

问题根源分析

  1. Curve组件中对象依赖的不稳定性:

    • const curve = { ...defaultCurve, ...props } 每次组件渲染都会创建新对象,导致useEffect的依赖curve每次引用都不同,触发useEffect重复执行。
    • yRange同理,每次渲染都是新创建的对象,同样会触发依赖变化。
    • 每次useEffect执行都会调用setAxis更新Context,导致CurveTrack重新渲染,进而引发Curve组件再次渲染,形成循环。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 07:44:54