React中接收后端API数据后调用setInterval实现OTP倒计时及修复重复渲染问题
问题根因
你遇到的重复渲染和倒计时异常问题来自3个核心错误:
- 定时器依赖设置错误:将
details.time作为定时器useEffect的依赖,会导致每次倒计时数值变化时都重新创建新的定时器,多个定时器叠加修改状态,引发重复渲染 - 闭包陷阱:定时器回调中直接读取
details.time,只会拿到定时器创建时的旧值,无法正确递减 - 未清理定时器:组件卸载或者定时器提前终止时没有清除旧定时器,会引发内存泄漏和状态异常
修复后的OTP组件完整代码
import { useDispatch, useSelector } from 'react-redux'; import { useState, useEffect, useRef } from 'react'; import { requestOTP } from '你的action路径'; const OTP = () => { const dispatch = useDispatch(); // 优化选择器,只订阅需要的refCode,避免Redux其他属性变化触发无效重渲染 const otpRef = useSelector(state => state?.otp?.refCode); const [otpDetails, setOtpDetails] = useState({ remainingTime: 0, // 初始为0,按钮默认可用 otpRef: undefined, otp: '' }); // 用useRef存储定时器ID,跨渲染周期可访问 const timerRef = useRef(null); useEffect(() => { if (otpRef) { // 先清理之前可能存在的旧定时器 if (timerRef.current) clearInterval(timerRef.current); const initCountdown = 60; // 初始化倒计时状态 setOtpDetails(prev => ({ ...prev, otpRef, remainingTime: initCountdown })); // 启动定时器 timerRef.current = setInterval(() => { setOtpDetails(prev => { if (prev.remainingTime <= 1) { // 倒计时到0,清除定时器 clearInterval(timerRef.current); timerRef.current = null; return {...prev, remainingTime: 0}; } // 函数式更新状态,避开闭包陷阱 return {...prev, remainingTime: prev.remainingTime - 1}; }); }, 1000); } // 依赖变化、组件卸载时自动清理定时器 return () => { if (timerRef.current) clearInterval(timerRef.current); }; }, [otpRef]); return ( <div> <input value={otpDetails.otp} onChange={(e) => setOtpDetails(prev => ({...prev, otp: e.target.value}))} /> <button onClick={() => dispatch(requestOTP())} disabled={otpDetails.remainingTime > 0} > {otpDetails.remainingTime > 0 ? `重新发送(${otpDetails.remainingTime}s)` : 'Request OTP'} </button> <span>Time remaining: {otpDetails.remainingTime}</span> </div> ) }
核心修改说明
- 状态更新统一用函数式写法
setOtpDetails(prev => ...),保证每次拿到的都是最新状态,避开闭包陷阱 - 定时器ID存在useRef中,跨渲染周期可访问,每次启动新定时器前先清理旧实例,不会出现多个定时器叠加运行的问题
- useEffect返回函数自动处理清理逻辑,组件卸载、依赖变化时都会自动销毁定时器,避免内存泄漏
- 按钮禁用逻辑直接绑定
remainingTime > 0,不需要额外状态管理
内容的提问来源于stack exchange,提问作者Earn Asdsa
相关产品推荐
相关产品推荐

