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
相关产品推荐
相关产品推荐

