Реагировать на зависание приложения при вызове setInterval - PullRequest
1 голос
/ 15 марта 2020

Я пытаюсь реализовать «Игру жизни» Конвея в React, но она замерзает при вызове нового поколения. Я предполагаю, что это связано с тем, что из-за постоянного повторного рендеринга DOM возникает слишком много накладных расходов, но я не знаю, как решить эту проблему, и не могу придумать альтернативы простой публикации всего моего кода, поэтому я заранее извиняюсь за многословие.

import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
import styled from "styled-components"

interface TileProps {
    bool: boolean
}

const Tile: React.FC<TileProps> = ({bool}) => {
    const colour = bool  == true ? "#00FF7F" : "#D3D3D3" 
    return (
        <div style = {{backgroundColor: colour}}/>
    )
}

interface GridProps {
    cells: boolean[][]
}

const StyledGrid = styled.div`
    display: grid;
    grid-template-columns: repeat(100, 1%);
    height: 60vh;
    width: 60vw;
    margin: auto;
    position: relative;
    background-color: #E182A8;
`

const Grid: React.FC<GridProps> = ({cells}) => {
    return (
        <StyledGrid>
            {cells.map(row => row.map(el => <Tile bool = {el}/>))}
        </StyledGrid>
    )
}

const randomBoolean = (): boolean => {
  const states = [true, false];
  return states[Math.floor(Math.random() * states.length)]
}

const constructCells = (rows: number, columns: number): boolean[][] => {
  return constructEmptyMatrix(rows, columns).map(row => row.map(e => randomBoolean()))
}

const constructEmptyMatrix = (rows: number, columns: number): number[][] => {
  return [...Array(rows)].fill(0).map(() => [...Array(columns)].fill(0));
}

const App: React.FC = () => {
  const columns = 100;
  const rows = 100;
  const [cells, updateCells] = useState<boolean[][]>(constructCells(rows, columns));

  useEffect(() => {
    const interval = setInterval(() => {
      newGeneration();
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  const isRowInGrid = (i: number): boolean => 0 <= i && i <= rows - 1

  const isColInGrid = (j : number): boolean => 0 <= j && j <= columns -1

  const isCellInGrid = (i: number, j: number): boolean => {
    return isRowInGrid(i) && isColInGrid(j)
  }

  const numberOfLiveCellNeighbours = (i: number, j: number): number => {
    const neighbours = [
      [i - 1, j], [i, j + 1], [i - 1, j + 1], [i - 1, j + 1],
      [i + 1, j], [i, j - 1], [i + 1, j - 1], [i + 1, j + 1]
    ]
    const neighboursInGrid = neighbours.filter(neighbour => isCellInGrid(neighbour[0], neighbour[1]))
    const liveNeighbours = neighboursInGrid.filter(x => {
      const i = x[0]
      const j = x[1]
      return cells[i][j] == true
    })
    return liveNeighbours.length;
  }

  const updateCellAtIndex = (i: number, j: number, bool: boolean) => {
    updateCells(oldCells => {
      oldCells = [...oldCells]
      oldCells[i][j] = bool;
      return oldCells;
    })
  }

  const newGeneration = (): void => {
    cells.map((row, i) => row.map((_, j) => {
      const neighbours = numberOfLiveCellNeighbours(i, j);
      if (cells[i][j] == true){
        if (neighbours < 2){
          updateCellAtIndex(i, j, false);
        } else if (neighbours <= 3){
          updateCellAtIndex(i, j, true);
        }
        else {
          updateCellAtIndex(i, j, false);
        }
      } else {
        if (neighbours === 3){
          updateCellAtIndex(i, j, true);
        }
      }
    }))
  }

  return (
    <div>
      <Grid cells = {cells}/>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'));

1 Ответ

3 голосов
/ 15 марта 2020

Приложение зависает, потому что React не производит пакетное обновление ваших состояний. Более подробную информацию об этом можно найти в этом ответе

У вас есть два варианта здесь.

  1. Использование ReactDOM.unstable_batchedUpdates:

Это можно сделать с помощью однострочного изменения, но учтите, что этот метод не является частью публичного c API

  useEffect(() => {
    const interval = setInterval(() => {
      // wrap generation function into batched updates
      ReactDOM.unstable_batchedUpdates(() => newGeneration())
    }, 1000);
    return () => clearInterval(interval);
  }, []);
Обновление всех состояний за одну операцию.

Вы можете изменить свой код, чтобы установить обновленные ячейки только один раз. Эта опция не использует нестабильные методы

  useEffect(() => {
    const interval = setInterval(() => {
       // `newGeneration` function needs to be refactored to remove all `updateCells` calls. It should update the input array and return the result
       const newCells = newGeneration(oldCells);
       // there will be only one call to React on each interval
       updateCells(newCells);
    }, 1000);
    return () => clearInterval(interval);
  }, []);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...