React中useEffect绑定keydown事件触发次数翻倍循环问题求助
问题原因与解决方案
核心问题
你的代码里useEffect没有设置依赖数组,导致每次组件状态更新(比如调用setDirection、setSquares触发重渲染)时,都会重新执行useEffect,重复添加新的keydown事件监听。随着操作次数增加,监听函数的数量会翻倍积累,最终导致按键触发多次回调,陷入循环。
另外,注释状态更新函数后仍有两次输出,是React开发环境下StrictMode的特性——它会强制组件渲染两次以检测副作用问题,所以会绑定两次监听,触发两次回调(生产环境不会出现该情况)。
修复步骤
1. 给useEffect添加空依赖数组
让useEffect仅在组件挂载时执行一次,卸载时移除监听,彻底避免重复绑定:
useEffect(()=> { // ... 监听逻辑 return () => document.removeEventListener('keydown', handleKeyDown); }, []); // 空依赖数组,仅挂载/卸载时执行
2. 用函数式更新解决闭包问题
添加空依赖后,handleKeyDown里的squares、direction会一直是初始状态的闭包值,无法获取最新状态。需要使用React的函数式更新来获取实时状态:
- 替换
setDirection(nextDirection)为setDirection(prevDirection => { /* 基于prevDirection计算新值 */ }) - 替换
setSquares(nextSquares)为setSquares(prevSquares => { /* 基于prevSquares计算新值 */ })
3. 优化事件处理细节
- 废弃的
keyCode建议替换为标准的e.key(比如ArrowUp、ArrowLeft),兼容性更好 - 移除事件监听的第三个参数
true(捕获阶段触发),除非你明确需要捕获阶段处理事件
修改后的完整代码
const [direction, setDirection] = useState([0]); // 简化Array(1).fill(0)的写法 const directions = ["botNorth","botEast","botSouth","botWest"]; const [squares, setSquares] = useState(Array(5).fill().map(()=> Array(5).fill(null))); useEffect(()=> { const handleKeyDown = (e) =>{ console.log(e.key); // 用e.key替代e.which/keyCode setDirection(prevDirection => { let nextDir = [...prevDirection]; // 复制当前状态 if (e.key === 'ArrowLeft') { nextDir[0]--; if (nextDir[0] < 0) { nextDir[0] = 3; } } else if (e.key === 'ArrowUp') { // 上箭头逻辑 } else if (e.key === 'ArrowRight') { // 右箭头逻辑 } // 基于最新direction更新squares setSquares(prevSquares => { const nextSquares = prevSquares.map(row => [...row]); // 深拷贝二维数组,避免直接修改原状态 nextSquares[location[0]][location[1]] = <img className={directions[nextDir[0]]} src={bot}/>; return nextSquares; }); return nextDir; }); }; document.addEventListener('keydown', handleKeyDown); return () => document.removeEventListener('keydown', handleKeyDown); }, [location, bot]); // 添加依赖:location和bot如果是外部变量,必须加入依赖数组,避免闭包旧值
额外说明
- 二维数组拷贝:原代码里
squares.slice()只是浅拷贝,修改内部数组时会影响原状态,所以要用map(row => [...row])深拷贝每一行 - 如果
location或bot是组件内的状态/ props,必须加入useEffect的依赖数组,否则handleKeyDown会拿到旧值
内容的提问来源于stack exchange,提问作者PhilosophiCat
相关产品推荐
相关产品推荐

