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

为何这段React代码首次执行跳过索引1?控制台输出异常解析

问题

以下是我的React代码:

const [index, setIndex] = useState(0);
useEffect(()=>{
  if (index === 3){
      setIndex(0)
      console.log(index)
  }else{
    setTimeout(() => setIndex((index) => index + 1), 2000);
    console.log(index)
    
  }
  }, [index]);

我的问题是:为何这段代码首次执行时会跳过索引1?控制台输出结果为0 0 2 3 0 1 2 3 0,其中前两个0的输出逻辑我可以理解,但无法理解为何会出现2,后续的3 0 1 2等输出序列符合预期。我的需求是根据数组索引在DOM中切换显示对应的名称。

原因分析

问题出在React 18开发环境的严格模式上:

  • React 18严格模式会在开发阶段让组件挂载时重复执行一次useEffect(模拟卸载后重新挂载的场景),导致你设置了两个同时在2秒后触发的setTimeout。
  • 2秒后,两个定时器依次执行:第一个把index从0更新到1,第二个紧接着把index从1更新到2。
  • 虽然index=1时useEffect确实会执行并打印1,但状态更新太快,要么控制台日志被后续输出覆盖,要么视觉上直接跳过了index=1的DOM显示,让你觉得索引1被跳过了。

修复方案

核心是确保同一时间只有一个定时器运行,用useRef保存定时器ID,并在useEffect的清理函数中清除旧定时器:

import { useState, useEffect, useRef } from 'react';

const [index, setIndex] = useState(0);
const timerRef = useRef(null);

useEffect(() => {
  if (index === 3) {
    setIndex(0);
    console.log(index);
  } else {
    // 清除上一个定时器,避免多个定时器同时触发
    if (timerRef.current) {
      clearTimeout(timerRef.current);
    }
    // 保存新定时器ID
    timerRef.current = setTimeout(() => {
      setIndex(prev => prev + 1);
    }, 2000);
    console.log(index);
  }

  // 组件卸载或依赖变化时清除当前定时器
  return () => {
    if (timerRef.current) {
      clearTimeout(timerRef.current);
    }
  };
}, [index]);

修改后,每次index变化都会先清除旧定时器,确保只有一个定时器在计时,索引会按0→1→2→3→0的顺序正常切换,不会再出现跳索引的问题。

内容的提问来源于stack exchange,提问作者Habizsabi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 13:25:17