React中定时器结合useState/useEffect时状态不更新及DFS终止问题
问题分析与解决方案
用户代码
const handleButtonClick = () => { if (!active) { if (inputValue == '') { return } if (!nodeInGraph(graph, inputValue)) { return } } setActive(!active); }; useEffect(() => { let itvId; if (active) { console.log('start') const ADJ = RawAdjList(graph); setAlgoInfo(pushback(algoInfo,inputValue)); itvId = setInterval(() => { console.log(algoInfo); if (algoInfo.arr.length == 0){ return () => { clearInterval(itvId); setAlgoInfo(initAlgoInfo()); }; } const node = algoInfo.arr[0]; setAlgoInfo(popback(algoInfo)); setTimeout(()=>{ ADJ[node].array.forEach(n => { if(!algoInfo.path.includes(n)){ setAlgoInfo(addPath(algoInfo,n)); setAlgoInfo(pushback(algoInfo,n)); } }); },500); }, 800); } return () => { clearInterval(itvId); setAlgoInfo(initAlgoInfo()); }; }, [active]) /* graph:{nodes:[],edges:[]} ADJ:{[key:nodeid]:{[key:nodeid]:weight} algoInfo:{stack:[],path:[],} pushback: copy algoinfo and push node in copied algoinfo.stack. popback: copy algoinfo and pop node in copied algoinfo.stack. addpath: copy and push node in algoinfo.path */
核心问题
点击按钮激活DFS后,algoInfo状态无法更新,且栈为空时定时器仍持续运行;仅能通过再次点击按钮切换active状态终止执行。
解决方案
1. 正确更新algoInfo状态
问题根源是setInterval的闭包特性:它捕获的是useEffect首次执行时的algoInfo初始值,后续状态更新后,闭包内的algoInfo不会同步,导致所有状态操作都基于旧值。
解决方式:使用React状态的函数式更新
React的setState支持传入函数,函数参数为当前最新状态,确保每次操作都基于最新的algoInfo:
// 初始化栈 setAlgoInfo(prev => pushback(prev, inputValue)); // 弹出栈顶 setAlgoInfo(prev => popback(prev)); // 添加路径节点 setAlgoInfo(prev => addPath(prev, n)); // 推入新节点到栈 setAlgoInfo(prev => pushback(prev, n));
2. 栈为空时终止DFS
当前代码的栈空判断基于旧的algoInfo值,且返回清理函数的写法不会立即终止定时器。需改用useRef跟踪最新状态,直接触发定时器清理:
步骤1:用Ref同步最新状态
const algoInfoRef = useRef(algoInfo); useEffect(() => { algoInfoRef.current = algoInfo; }, [algoInfo]);
步骤2:在定时器内判断栈状态并终止
itvId = setInterval(() => { const currentInfo = algoInfoRef.current; // 修正注释中的笔误:arr应为stack if (currentInfo.stack.length === 0) { clearInterval(itvId); setAlgoInfo(initAlgoInfo()); return; } // 后续状态操作全部改用函数式更新 setAlgoInfo(prev => { const poppedState = popback(prev); const currentNode = prev.stack[0]; setTimeout(() => { setAlgoInfo(innerPrev => { let updated = innerPrev; ADJ[currentNode].array.forEach(n => { if (!updated.path.includes(n)) { updated = addPath(updated, n); updated = pushback(updated, n); } }); return updated; }); }, 500); return poppedState; }); }, 800);
完整修正代码示例
import { useState, useEffect, useRef } from 'react'; const YourComponent = () => { const [active, setActive] = useState(false); const [inputValue, setInputValue] = useState(''); const [graph, setGraph] = useState({ nodes: [], edges: [] }); const [algoInfo, setAlgoInfo] = useState({ stack: [], path: [] }); const algoInfoRef = useRef(algoInfo); // 同步最新状态到Ref useEffect(() => { algoInfoRef.current = algoInfo; }, [algoInfo]); const handleButtonClick = () => { if (!active) { if (inputValue === '') return; if (!nodeInGraph(graph, inputValue)) return; } setActive(!active); }; useEffect(() => { let itvId; if (active) { console.log('start'); const ADJ = RawAdjList(graph); setAlgoInfo(prev => pushback(prev, inputValue)); itvId = setInterval(() => { const currentInfo = algoInfoRef.current; if (currentInfo.stack.length === 0) { clearInterval(itvId); setAlgoInfo(initAlgoInfo()); return; } setAlgoInfo(prev => { const poppedState = popback(prev); const currentNode = prev.stack[0]; setTimeout(() => { setAlgoInfo(innerPrev => { let updated = innerPrev; ADJ[currentNode].array.forEach(n => { if (!updated.path.includes(n)) { updated = addPath(updated, n); updated = pushback(updated, n); } }); return updated; }); }, 500); return poppedState; }); }, 800); } return () => { if (itvId) clearInterval(itvId); setAlgoInfo(initAlgoInfo()); }; }, [active, graph, inputValue]); // 请自行实现以下辅助函数:nodeInGraph, RawAdjList, pushback, popback, addPath, initAlgoInfo return ( <div> <input value={inputValue} onChange={(e) => setInputValue(e.target.value)} /> <button onClick={handleButtonClick}>{active ? '停止DFS' : '开始DFS'}</button> </div> ); };
内容的提问来源于stack exchange,提问作者nayounsang
相关产品推荐
相关产品推荐

