如何修复React中用setInterval和setTimeout实现组件顺序加载的异常问题?
问题根源分析
- 第一个问题(组件1、2同时加载):你的
analyseDNA里用了固定延迟的setTimeout,没有等前一个组件进度到100%就触发下一个,而且setTimeout(setLoading2(true), 2000)写法错误,你直接执行了setLoading2(true)而不是把它作为回调传给setTimeout,相当于代码执行到这行就直接触发了状态更新,所以第二个组件提前进入加载状态。 - 第二个问题(二次点击仅组件1加载):二次点击时没有重置
progress2、progress3和对应的loading状态,之前的进度已经是100%,就算触发加载逻辑也不会有变化。 - 第三个问题(定时器不终止):load函数返回的清除定时器的函数没有被调用,而且进度到100%的时候没有主动清除定时器,导致定时器一直循环运行。
修复方案
1. 先定义基础状态和定时器存储
import { useState, useEffect, useRef, useCallback } from 'react'; // 组件内代码 const [progress1, setProgress1] = useState(0); const [progress2, setProgress2] = useState(0); const [progress3, setProgress3] = useState(0); const [loading, setLoading] = useState(false); const [loading2, setLoading2] = useState(false); const [loading3, setLoading3] = useState(false); // 存储所有定时器引用,方便统一清理 const timerRefs = useRef({ timer1: null, timer2: null, timer3: null });
2. 封装通用加载逻辑避免重复代码
// 通用加载函数,传入修改进度的setter和对应的定时器key const createLoadFn = useCallback((setProgress, timerKey) => { // 先清除之前的同类型定时器,避免重复启动 if (timerRefs.current[timerKey]) { clearInterval(timerRefs.current[timerKey]); } timerRefs.current[timerKey] = setInterval(() => { setProgress(prev => { if (prev >= 100) { // 进度到100%立刻清除定时器 clearInterval(timerRefs.current[timerKey]); timerRefs.current[timerKey] = null; return 100; } return prev + 10; }) }, 800); }, []);
3. 关联加载顺序,实现前一个完成再加载后一个
// 监听progress1到100,触发第二个组件加载 useEffect(() => { if (progress1 === 100) { setLoading2(true); createLoadFn(setProgress2, 'timer2'); } }, [progress1, createLoadFn]); // 监听progress2到100,触发第三个组件加载 useEffect(() => { if (progress2 === 100) { setLoading3(true); createLoadFn(setProgress3, 'timer3'); } }, [progress2, createLoadFn]); // 组件卸载时清理所有定时器,避免内存泄漏 useEffect(() => { return () => { Object.values(timerRefs.current).forEach(timer => timer && clearInterval(timer)); }; }, []);
4. 修改启动函数,增加状态重置逻辑
const analyseDNA = useCallback(() => { // 先清空所有定时器 Object.values(timerRefs.current).forEach(timer => timer && clearInterval(timer)); // 重置所有进度和loading状态 setProgress1(0); setProgress2(0); setProgress3(0); setLoading2(false); setLoading3(false); // 2秒后启动第一个组件加载 setTimeout(() => { createLoadFn(setProgress1, 'timer1'); }, 2000); }, [createLoadFn]);
5. 按钮代码保持不变即可
<Button variant="outlined" type="submit" onClick={() => { setLoading(true); analyseDNA(); }} > Start </Button>
修复说明
- 解决同步加载问题:通过useEffect监听前一个组件的进度值,只有前一个到100%才触发下一个加载,替代原来的固定延迟setTimeout逻辑,不受加载速度波动影响
- 解决二次点击无响应问题:每次点击启动时先重置所有进度、loading状态,清空之前的定时器,保证每次加载都是全新的流程
- 解决定时器泄漏问题:进度到100%时主动清除对应定时器,组件卸载时统一清理所有剩余定时器,不会有残留的定时器一直在后台运行
内容的提问来源于stack exchange,提问作者S. N
相关产品推荐
相关产品推荐

