React实现2048游戏:removeEventListener失效,事件触发次数翻倍
问题根源与修复方案
核心问题
- 事件监听器无法被正确移除:你的
useEffect依赖globalBoard,每次棋盘状态更新都会重新执行该useEffect。每次执行时都会创建全新的handleGame函数,而removeEventListener需要和添加时完全相同的函数引用才能生效,导致旧的监听器永远无法被清理,每次按键都会触发所有累积的监听器,次数呈指数增长。 - 错误的事件绑定逻辑:绑定事件时使用了匿名函数
(e)=> handleGame(e,board),这个匿名函数每次useEffect执行都是新的引用,即使尝试移除handleGame,也无法匹配到这个匿名函数,进一步加剧了监听器堆积。
修复步骤
步骤1:重构事件处理函数,稳定引用并依赖状态
将handleGame改为直接使用当前的globalBoard状态,并用useCallback包裹确保函数引用稳定,避免每次状态更新都生成新函数:
const handleGame = useCallback((e) => { if (!globalBoard) return; // 复制棋盘,避免直接修改状态 let board = [[...globalBoard[0]], [...globalBoard[1]], [...globalBoard[2]], [...globalBoard[3]]]; // 原方向键处理逻辑保持不变 if (!["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) return; if (e.key === "ArrowUp") { // 原有向上移动逻辑 } // 其他方向键逻辑... // 检查棋盘变化并更新状态 let arrayChanged = false; outerLoop: for (let i = 0; i < 4; i++) { for (let j = 0; j < 4; j++) { if (globalBoard[i][j] !== board[i][j]) { arrayChanged = true; break outerLoop; } } } if (arrayChanged) { // 生成新数字逻辑保持不变 let number1 = Math.floor(Math.random() * 4); let number2 = Math.floor(Math.random() * 4); while (board[number1][number2] !== 0) { number1 = Math.floor(Math.random() * 4); number2 = Math.floor(Math.random() * 4); } board[number1][number2] = Math.floor(Math.random() * 10) >= 8 ? 4 : 2; setGlobalBoard(board); } }, [globalBoard]);
步骤2:调整useEffect,仅绑定一次监听器
将事件监听器的添加放在仅执行一次的useEffect中(依赖handleGame确保引用更新时能重新绑定),卸载时正确移除:
useEffect(() => { window.addEventListener("keydown", handleGame); return () => window.removeEventListener("keydown", handleGame); }, [handleGame]);
步骤3:移除原有错误的useEffect
删除原来依赖globalBoard的useEffect,因为现在监听器仅绑定一次,且handleGame通过useCallback依赖globalBoard,能获取到最新状态。
额外优化:修复列表key问题
避免用uuid生成key(每次渲染都会生成新key,导致React频繁销毁重建DOM),改用行列索引组合作为key:
return ( <div className="board"> {globalBoard && globalBoard.map((array, rowIndex) => { return array.map((el, colIndex) => { return <div key={`${rowIndex}-${colIndex}`} className="element">{el === 0 ? "" : el}</div> }) })} </div> );
完整修复后的代码
import "./Board.css" import { useEffect, useState, useCallback } from "react"; const Board = () => { const [globalBoard, setGlobalBoard] = useState(null); useEffect(() => { initializeGame(); }, []); const handleGame = useCallback((e) => { if (!globalBoard) return; if (!["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) return; let board = [[...globalBoard[0]], [...globalBoard[1]], [...globalBoard[2]], [...globalBoard[3]]]; if (e.key === "ArrowUp") { for (let i = 0; i < 3; i++) { for (let j = 0; j < 4; j++) { let k = 0; while (board[i][j] === 0 && k < 3) { if (i === 0) { board[i][j] = board[i + 1][j]; board[i + 1][j] = board[i + 2][j]; board[i + 2][j] = board[i + 3][j]; board[i + 3][j] = 0; } if (i === 1) { board[i][j] = board[i + 1][j]; board[i + 1][j] = board[i + 2][j]; board[i + 2][j] = 0; } if (i === 2) { board[i][j] = board[i + 1][j]; board[i + 1][j] = 0; } k++; } } } for (let i = 0; i < 3; i++) { for (let j = 0; j < 4; j++) { if (board[i][j] !== 0 && board[i][j] === board[i + 1][j]) { board[i][j] *= 2; if (i === 0) { board[i + 1][j] = board[i + 2][j]; board[i + 2][j] = board[i + 3][j]; board[i + 3][j] = 0; } if (i === 1) { board[i + 1][j] = board[i + 2][j]; board[i + 2][j] = 0; } if (i === 2) { board[i + 1][j] = 0; } } } } } if (e.key === "ArrowDown") { for (let i = 3; i > 0; i--) { for (let j = 0; j < 4; j++) { let k = 0; while (board[i][j] === 0 && k < 3) { if (i === 3) { board[i][j] = board[i - 1][j]; board[i - 1][j] = board[i - 2][j]; board[i - 2][j] = board[i - 3][j]; board[i - 3][j] = 0; } if (i === 2) { board[i][j] = board[i - 1][j]; board[i - 1][j] = board[i - 2][j]; board[i - 2][j] = 0; } if (i === 1) { board[i][j] = board[i - 1][j]; board[i - 1][j] = 0; } k++; } } } for (let i = 3; i > 0; i--) { for (let j = 0; j < 4; j++) { if (board[i][j] !== 0 && board[i][j] === board[i - 1][j]) { board[i][j] *= 2; if (i === 3) { board[i - 1][j] = board[i - 2][j]; board[i - 2][j] = board[i - 3][j]; board[i - 3][j] = 0; } if (i === 2) { board[i - 1][j] = board[i - 2][j]; board[i - 2][j] = 0; } if (i === 1) { board[i - 1][j] = 0; } } } } } if (e.key === "ArrowLeft") { for (let i = 0; i < 4; i++) { for (let j = 0; j < 3; j++) { let k = 0; while (board[i][j] === 0 && k < 3) { if (j === 0) { board[i][j] = board[i][j + 1]; board[i][j + 1] = board[i][j + 2]; board[i][j + 2] = board[i][j + 3]; board[i][j + 3] = 0; } if (j === 1) { board[i][j] = board[i][j + 1]; board[i][j + 1] = board[i][j + 2]; board[i][j + 2] = 0; } if (j === 2) { board[i][j] = board[i][j + 1]; board[i][j + 1] = 0; } k++; } } } for (let i = 0; i < 4; i++) { for (let j = 0; j < 3; j++) { if (board[i][j] !== 0 && board[i][j] === board[i][j + 1]) { board[i][j] *= 2; if (j === 0) { board[i][j + 1] = board[i][j + 2]; board[i][j + 2] = board[i][j + 3]; board[i][j + 3] = 0; } if (j === 1) { board[i][j + 1] = board[i][j + 2]; board[i][j + 2] = 0; } if (j === 2) { board[i][j + 1] = 0; } } } } } if (e.key === "ArrowRight") { for (let i = 0; i < 4; i++) { for (let j = 3; j > 0; j--) { let k = 0; while (board[i][j] === 0 && k < 3) { if (j === 3) { board[i][j] = board[i][j - 1]; board[i][j - 1] = board[i][j - 2]; board[i][j - 2] = board[i][j - 3]; board[i][j - 3] = 0; } if (j === 2) { board[i][j] = board[i][j - 1]; board[i][j - 1] = board[i][j - 2]; board[i][j - 2] = 0; } if (j === 1) { board[i][j] = board[i][j - 1]; board[i][j - 1] = 0; } k++; } } } for (let i = 0; i < 4; i++) { for (let j = 3; j > 0; j--) { if (board[i][j] !== 0 && board[i][j] === board[i][j - 1]) { board[i][j] *= 2; if (j === 3) { board[i][j - 1] = board[i][j - 2]; board[i][j - 2] = board[i][j - 3]; board[i][j - 3] = 0; } if (j === 2) { board[i][j - 1] = board[i][j - 2]; board[i][j - 2] = 0; } if (j === 1) { board[i][j - 1] = 0; } } } } } let arrayChanged = false; outerLoop: for (let i = 0; i < 4; i++) { for (let j = 0; j < 4; j++) { if (globalBoard[i][j] !== board[i][j]) { arrayChanged = true; break outerLoop; } } } if (arrayChanged) { let number1 = Math.floor(Math.random() * 4); let number2 = Math.floor(Math.random() * 4); while (board[number1][number2] !== 0) { number1 = Math.floor(Math.random() * 4); number2 = Math.floor(Math.random() * 4); } board[number1][number2] = Math.floor(Math.random() * 10) >= 8 ? 4 : 2; setGlobalBoard(board); } }, [globalBoard]); useEffect(() => { window.addEventListener("keydown", handleGame); return () => window.removeEventListener("keydown", handleGame); }, [handleGame]); const initializeGame = () => { let board = []; for (let i = 0; i < 4; i++) { board.push([]); for (let j = 0; j < 4; j++) { board[i].push(0); } } let randomNr1 = Math.floor(Math.random() * 4); let randomNr2 = Math.floor(Math.random() * 4); board[randomNr1][randomNr2] = 2; let newRandomNr1, newRandomNr2; do { newRandomNr1 = Math.floor(Math.random() * 4); newRandomNr2 = Math.floor(Math.random() * 4); } while (randomNr1 === newRandomNr1 && randomNr2 === newRandomNr2); board[newRandomNr1][newRandomNr2] = Math.floor(Math.random() * 10) >= 8 ? 4 : 2; setGlobalBoard(board); return board; }; return ( <div className="board"> {globalBoard && globalBoard.map((array, rowIndex) => { return array.map((el, colIndex) => { return <div key={`${rowIndex}-${colIndex}`} className="element">{el === 0 ? "" : el}</div> }) })} </div> ); }; export default Board;
内容的提问来源于stack exchange,提问作者Jakub Godlewski
相关产品推荐
相关产品推荐

