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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 01:45:03