React滑块定时器异常:点击切换问题后间隔变为1秒而非3秒
滑块自动切换问题排查与修复
需求概述
正在实现一个滑块功能,核心需求:
- 每3秒自动切换到下一个问题(基础功能正常)
- 鼠标进入:停止计时器,CSS动画停止(已实现)
- 鼠标离开:从剩余时间处重新启动计时器
- 点击:切换到选中的问题(已实现)并启动动画
当前问题
点击切换question_number后,下一个问题每隔1秒就切换一次,而非预期的3秒,推测和React重渲染有关。
现有代码
状态定义
const [timeSlider, setTimeSlider] = useState({ question_length: 4, question_number: 0, current_time: 0 });
计时器Ref
const interval = useRef(null);
初始化计时器的Effect
useEffect(() => { setRemaining({ start_time: Date.now(), stop_time: 0 }); interval.current = setInterval(() => { setSeconds(seconds => seconds + 1); }, 1000); pauseSlider(); return () => clearInterval(interval); }, []);
秒数变化触发切换的Effect
useEffect(() => { if (seconds === 3) { setSeconds(seconds => 0); setTimeSlider(prev => prev.question_number === 3 ? { ...timeSlider, question_number: 0 } : { ...timeSlider, question_number: timeSlider.question_number + 1 }); } }, [seconds]);
暂停、恢复与切换函数
function pauseSlider() { setSliderHover(true) setRemaining({ start_time: 0, stop_time: Date.now() }); clearInterval(interval.current); } function resumeSlider() { setSliderHover(false) setRemaining(prev => ({ ...prev, start_time: prev.stop_time, stop_time: 0 })); interval.current = setInterval(() => { setSeconds(seconds => seconds + 1); }, 1000); } function updateNumber(num) { setSliderHover(false) setSeconds(0) setRemaining({ start_time: 0, stop_time: 0 }); clearInterval(interval.current); interval.current = setInterval(() => { setSeconds(seconds => seconds + 1); }, 1000); setTimeSlider({ ...timeSlider, question_number: num }); }
问题分析与修复方案
核心问题1:闭包与状态依赖错误
在setTimeSlider中直接引用外部的timeSlider状态,这会导致闭包捕获旧的状态快照,当组件重渲染后,切换逻辑会使用旧值引发异常;同时useEffect的依赖数组缺少timeSlider,触发时机不符合预期。
修复:完全使用函数式更新依赖prev参数,避免直接引用外部状态:
useEffect(() => { if (seconds === 3) { setSeconds(0); setTimeSlider(prev => ({ ...prev, question_number: prev.question_number === prev.question_length - 1 ? 0 : prev.question_number + 1 })); } }, [seconds]);
核心问题2:计时器未彻底清除
点击updateNumber时,旧计时器可能未被及时清除,导致新旧计时器同时运行,造成秒数快速累加,进而触发频繁切换。
修复:每次创建新计时器前,确保彻底清除旧实例:
// 初始化Effect的清理函数修正 return () => clearInterval(interval.current); // updateNumber函数优化 function updateNumber(num) { setSliderHover(false); setSeconds(0); setRemaining({ start_time: 0, stop_time: 0 }); if (interval.current) { clearInterval(interval.current); interval.current = null; } interval.current = setInterval(() => { setSeconds(prev => prev + 1); }, 1000); setTimeSlider(prev => ({ ...prev, question_number: num })); }
额外优化:简化计时器逻辑
可以直接使用3秒计时器控制切换,无需维护seconds状态,减少不必要的重渲染:
const interval = useRef(null); const startAutoSwitch = () => { if (interval.current) clearInterval(interval.current); interval.current = setInterval(() => { setTimeSlider(prev => ({ ...prev, question_number: prev.question_number === prev.question_length - 1 ? 0 : prev.question_number + 1 })); }, 3000); }; useEffect(() => { startAutoSwitch(); pauseSlider(); return () => clearInterval(interval.current); }, []); function pauseSlider() { setSliderHover(true); if (interval.current) { clearInterval(interval.current); interval.current = null; } } function resumeSlider() { setSliderHover(false); startAutoSwitch(); } function updateNumber(num) { setSliderHover(false); setTimeSlider(prev => ({ ...prev, question_number: num })); startAutoSwitch(); }
内容的提问来源于stack exchange,提问作者Tahir Musharraf
相关产品推荐
相关产品推荐

