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

React实现2048游戏:removeEventListener失效,事件触发次数翻倍

问题根源与修复方案

核心问题

  1. 事件监听器无法被正确移除:你的useEffect依赖globalBoard,每次棋盘状态更新都会重新执行该useEffect。每次执行时都会创建全新的handleGame函数,而removeEventListener需要和添加时完全相同的函数引用才能生效,导致旧的监听器永远无法被清理,每次按键都会触发所有累积的监听器,次数呈指数增长。
  2. 错误的事件绑定逻辑:绑定事件时使用了匿名函数(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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 00:45:28