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
相关产品推荐
相关产品推荐

