Не удается устранить ненужные повторные рендеринги с помощью memo

#javascript #reactjs #state #rendering #memo

Вопрос:

Я недавно начал изучать React и создаю игру 2048 года. Теперь всякий раз, когда пользователь выполняет перемещение, я вношу определенные изменения в сетку, которая, следовательно, обновляет состояние. Однако при каждом последующем нажатии клавиши количество повторных отображений сетки увеличивается экспоненциально и зависает после определенной точки, даже если состояние (значение сетки) не меняется. Проблема сохраняется даже после использования React.memo. Как мне устранить эти нежелательные повторные визуализации и почему это происходит?

 import React, { useState, useEffect } from "react";
import Cell from "./Cell";
import "./Game.css";

let permittedKeys = [
  "ArrowUp",
  "KeyW",
  "ArrowRight",
  "KeyD",
  "ArrowDown",
  "KeyS",
  "ArrowLeft",
  "KeyA",
];

const boardSize = 4;

function Game() {
  const [grid, setGrid] = useState();

  console.log("re-rendering grid", grid);
  //creating a 4x4 board when component mounts
  useEffect(() => {
    const board = getBoard(boardSize);
    setGrid(board);
  }, []);

  //function that renders all the 4x4 cells
  // CORRECTION - Give every cell a key
  const renderCells =
    grid amp;amp;
    grid.map((row, i) => row.map((col, j) => <Cell key={i*boardSize j} value={grid[i][j]} />));

  //check if a key is pressed
  document.addEventListener("keydown", (e) => {
    //If pressed key is not a valid game control key ignore the keydown event
    if (permittedKeys.indexOf(e.code) === -1) return;

    //Else
    if (grid) setGrid(performMove(e.code, [...grid], boardSize));
  });

  return <div className="board">{renderCells}</div>;
}

const performMove = (pressedKey, board, boardSize) => {
  //Move cells in pressedKey-direction till they hit another block
  //Eg:When left key is pressed
  //256 empty 256 64 becomes
  //256 256 64 empty
  for (let i = 0; i < boardSize;   i) {
    const newRow = [];
    let count = 0;

    for (let j = 0; j < boardSize;   j)
      if (board[i][j]) {
        newRow.push(board[i][j]);
          count;
      }
    while (count !== boardSize) {
        count;
      if (pressedKey === permittedKeys[6] || pressedKey === permittedKeys[7])
        newRow.push(0);
      else if (
        pressedKey === permittedKeys[2] ||
        pressedKey === permittedKeys[3]
      )
        newRow.unshift(0);
    }
    board[i] = newRow;
  }
  return board;
};

//generate the next cell after the player performs a move
function generateNext() {
  return Math.random() < 0.9 ? 2 : 4;
}

function getBoard(boardSize) {
  const board = [];
  for (let i = 0; i < boardSize;   i) {
    const row = [];
    for (let j = 0; j < 4;   j) row.push(0);
    board.push(row);
  }
  //generating two random starting cells
  let i1 = Math.floor(Math.random() * boardSize);
  let j1 = Math.floor(Math.random() * boardSize);
  let i2, j2;
  do {
    i2 = Math.floor(Math.random() * boardSize);
    j2 = Math.floor(Math.random() * boardSize);
  } while (i1 === i2 amp;amp; j1 === j2);

  board[i1][j1] = generateNext();
  board[i2][j2] = generateNext();

  return board;
}

export default React.memo(Game);
 

Комментарии:

1. При каждом рендеринге вы добавляете нового keydown слушателя, поэтому при каждом рендеринге вы получаете экспоненциально больше setGrid вызовов. Вы не должны добавлять слушателей напрямую, а скорее должны использовать цикл событий/рендеринга Reacts.

2. @pilchard, это сработало для меня. Я добавил прослушиватель событий, когда компонент был смонтирован, вместо того, чтобы делать это в функциональном компоненте, и это решило мою проблему. Спасибо!