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

React中依赖未变更时useEffect仍重复触发的问题

问题原因与解决方案

问题原因

当你切换步进器到其他步骤时,FormWrapper会通过switch语句卸载当前非激活步骤的组件(比如从TIMESPAN切到INCOME时,Timespan组件会被完全卸载)。当你切回TIMESPAN步骤时,Timespan组件会重新挂载,此时组件内的useEffect会在挂载阶段执行——这就是为什么即使personId没变,每次切回都会重新发起请求的原因。

解决方案

方案1:保持所有表单组件挂载,仅隐藏非激活组件

修改FormWrapper,让所有子表单组件始终保持挂载状态,通过display属性控制显示/隐藏,避免组件卸载后重新挂载:

export default function FormWrapper({ activeStep, ...props }: FormWrapperProps) {
  return (
    <>
      <Timespan 
        {...props} 
        style={{ display: activeStep === Stepper.TIMESPAN ? 'block' : 'none' }} 
      />
      <Income 
        {...props} 
        style={{ display: activeStep === Stepper.INCOME ? 'block' : 'none' }} 
      />
      <Relationship 
        {...props} 
        style={{ display: activeStep === Stepper.RELATIONSHIP ? 'block' : 'none' }} 
      />
      <Decision 
        {...props} 
        style={{ display: activeStep === Stepper.DECISION ? 'block' : 'none' }} 
      />
    </>
  );
}

这样组件不会被卸载,useEffect只会在首次挂载和personId变更时触发,切换步骤不会重复请求。

方案2:将数据缓存到父组件

把数据请求逻辑上移到父组件,缓存请求结果,再通过props传递给子组件。这样即使子组件卸载,数据依然保存在父组件中:

// 父组件代码
import { useState, useEffect, useMemo } from 'react';

export default function ParentComponent() {
  const [activeStep, setActiveStep] = useState(...);
  const [personId, setPersonId] = useState(...);
  const [period, setPeriod] = useState<PeriodResponse[]>([]);
  const periodService = useMemo(() => new PeriodService(), []);

  useEffect(() => {
    const periodDtoPromises = [getFullYear() - 1, getFullYear() - 2, getFullYear() - 3].map((year) =>
      periodService.getPeriod({
        incomeYear: year.toString(),
        personId,
      })
    );

    Promise.all(periodDtoPromises)
      .then((res) => setPeriod(res))
      .catch((error) => console.error(error.message));
  }, [personId, periodService]);

  return (
    <>
      {/* 步进器代码 */}
      <Stepper ... />
      <FormWrapper 
        setActiveStep={setActiveStep} 
        activeStep={activeStep} 
        personId={personId} 
        period={period} 
      />
    </>
  );
}

// Timespan组件代码
export default function Timespan({ setActiveStep, period }: CommonFormProps & { period: PeriodResponse[] }) {
  // 直接使用props传递的period即可,无需自己请求
}

方案3:组件内部缓存数据

在Timespan组件内用useRef缓存已请求的数据和对应的personId,判断如果当前personId未变更且已有缓存,就直接复用数据:

export default function Timespan({ setActiveStep, personId }: CommonFormProps) {
  const [period, setPeriod] = useState<PeriodResponse[]>([]);
  const cachedPersonId = useRef<string | undefined>(personId);
  const cachedPeriod = useRef<PeriodResponse[]>([]);
  const periodService = new PeriodService();

  useEffect(() => {
    // 缓存存在且personId未变更,直接复用
    if (cachedPersonId.current === personId && cachedPeriod.current.length > 0) {
      setPeriod(cachedPeriod.current);
      return;
    }

    const periodDtoPromises = [getFullYear() - 1, getFullYear() - 2, getFullYear() - 3].map((year) =>
      periodService.getPeriod({
        incomeYear: year.toString(),
        personId,
      })
    );

    Promise.all(periodDtoPromises)
      .then((res) => {
        setPeriod(res);
        cachedPeriod.current = res;
        cachedPersonId.current = personId;
      })
      .catch((error) => {
        console.error(error.message);
      });
  }, [personId]);
}

内容的提问来源于stack exchange,提问作者Ludwig

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 15:50:30