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

React定时器执行完毕后state值显示undefined问题求助

打字应用定时器到期后state变量变为undefined的问题

我正在开发一款类似Typing Master的打字应用。在useEffect钩子中调用setInterval函数,当timeElapsed达到40秒时执行clearInterval,但定时器到期后,已定义的state变量值变为undefined,特此求助。

代码片段

// line number 17
const [testInfo, setTestInfo] = useState({
  timeElapsed: 0,
  paragraphFormed: "",
  totalKeysPressed: 0,
  WPM: 0,
  accuracy: 0,
});

useEffect(() => {
  const interval = setInterval(() => {
    setTestInfo((preVal) => {
      if (MAX_TIME - preVal.timeElapsed == 0) {
        return clearInterval(interval);
      }
      return {
        ...preVal,
        timeElapsed: preVal["timeElapsed"] + 1,
        WPM: calculateWPM(preVal.paragraphFormed, preVal.timeElapsed),
        accuracy: calculateAccuracy(
          preVal.totalKeysPressed,
          preVal.paragraphFormed.length
        ),
      };
    });
  }, 1000);
}, []);

const keyDownHandler = (event) => {
  let curLetter = "";
  let curtestInfo = {};
  // line number 86
  setTestInfo((preVal) => {
    if (event.keyCode === 32) {
      if (matchFound(formedCurWord, wordList[wordIndex])) {
        setFormedCurWord("");
        setTestInfo((preVal) => {
          return {
            ...preVal,
            totalKeysPressed: preVal["totalKeysPressed"] + 1,
          };
        });
        setwordIndex((preVal) => {
          return preVal + 1;
        });
        curLetter = " ";
        console.log("space pressed: ", formedCurWord);
        console.log("space pressed: ", preVal["paragraphFormed"]);
        return {
          ...preVal,
          paragraphFormed: preVal["paragraphFormed"].concat(curLetter),
        };
      }
    } else if (event.keyCode === 8) {
      console.log("backspace pressed");
      setFormedCurWord((preVal) => {
        return preVal.slice(0, -1);
      });
      curLetter = "";
      if (formedCurWord !== "") {
        return {
          ...preVal,
          paragraphFormed: preVal["paragraphFormed"].slice(0, -1),
        };
      } else {
        console.log("backspace pressed: ", formedCurWord);
      }
    } else if (allowedKey.includes(event.keyCode)) {
      console.log("you know a valid key is pressed");
      setTestInfo((preVal) => {
        return {
          ...preVal,
          totalKeysPressed: preVal["totalKeysPressed"] + 1,
        };
      });
      setTotalKeysPressed((preVal) => {
        return preVal + 1;
      });
      curLetter = event.key;
      setFormedCurWord((preVal) => {
        return preVal.concat(curLetter);
      });
      // this is line number 139
      return {
        ...preVal,
        paragraphFormed: preVal["paragraphFormed"].concat(curLetter),
      };
    }

    return {
      ...preVal,
      paragraphFormed: preVal["paragraphFormed"].concat(curLetter),
    };
  });
};

错误截图

错误截图


问题原因及解决方案

1. 核心错误:返回clearInterval的结果导致state变为undefined

在定时器到期的判断逻辑中,你写了return clearInterval(interval);,但clearInterval()的返回值是undefined,这会让setTestInfo把testInfo整个替换成undefined,这就是问题的根源。

修正后的useEffect代码:

useEffect(() => {
  const interval = setInterval(() => {
    setTestInfo((preVal) => {
      if (MAX_TIME - preVal.timeElapsed === 0) {
        // 先清理定时器,再返回正确的状态对象
        clearInterval(interval);
        return {
          ...preVal,
          // 可以在这里计算最终的WPM和准确率
          WPM: calculateWPM(preVal.paragraphFormed, preVal.timeElapsed),
          accuracy: calculateAccuracy(
            preVal.totalKeysPressed,
            preVal.paragraphFormed.length
          ),
        };
      }
      return {
        ...preVal,
        timeElapsed: preVal.timeElapsed + 1,
        WPM: calculateWPM(preVal.paragraphFormed, preVal.timeElapsed),
        accuracy: calculateAccuracy(
          preVal.totalKeysPressed,
          preVal.paragraphFormed.length
        ),
      };
    });
  }, 1000);
  // 增加组件卸载时的清理逻辑,避免内存泄漏
  return () => clearInterval(interval);
}, []);

2. 次要问题:keyDownHandler中嵌套调用setTestInfo

在keyDownHandler的回调里,你多次嵌套调用setTestInfo,这会导致状态更新队列混乱,因为React的状态更新是异步的,嵌套调用可能会依赖过期的状态值。应该把所有状态更新逻辑合并到同一个setTestInfo回调中。

修正后的keyDownHandler代码:

const keyDownHandler = (event) => {
  setTestInfo((preVal) => {
    let updatedTotalKeys = preVal.totalKeysPressed;
    let updatedParagraph = preVal.paragraphFormed;

    if (event.keyCode === 32) {
      if (matchFound(formedCurWord, wordList[wordIndex])) {
        updatedTotalKeys += 1;
        updatedParagraph += " ";
        setFormedCurWord("");
        setwordIndex(prev => prev + 1);
        console.log("space pressed: ", formedCurWord);
        console.log("space pressed: ", preVal.paragraphFormed);
      }
    } else if (event.keyCode === 8) {
      console.log("backspace pressed");
      setFormedCurWord(prev => prev.slice(0, -1));
      if (formedCurWord !== "") {
        updatedParagraph = updatedParagraph.slice(0, -1);
      } else {
        console.log("backspace pressed: ", formedCurWord);
      }
    } else if (allowedKey.includes(event.keyCode)) {
      console.log("you know a valid key is pressed");
      updatedTotalKeys += 1;
      const curLetter = event.key;
      updatedParagraph += curLetter;
      setFormedCurWord(prev => prev.concat(curLetter));
      // 如果totalKeysPressed已经在testInfo里,这个单独的state可以考虑移除
      setTotalKeysPressed(prev => prev + 1);
    }

    return {
      ...preVal,
      totalKeysPressed: updatedTotalKeys,
      paragraphFormed: updatedParagraph,
    };
  });
};

额外建议

如果你的状态更新逻辑越来越复杂,建议使用useReducer来管理testInfo,它比多个setState调用更适合处理复杂的状态流转,能避免嵌套更新的问题,代码也更易维护。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 04:06:27