在递归中使用setTimeout时,setState变慢

时间:2019-12-29 10:18:34

标签: reactjs algorithm

我一直在React中构建一个迷宫实现。基本逻辑是,每次我运行go函数,通过Array.from()将useState设置为一个新的100 * 100数组时,它就会检查其邻居是否可用。

这是我的迷宫外观

enter image description here

  const App: React.FC = () => {
  const Maze: MazeConstructor = MazeData;
  const maze: IMaze = new Maze(mazeJson);
  const [data, setData] = useState(maze.maze);

  const handleClick = (): void => {
    go(maze.entranceX, maze.entranceY);
  }

  const go = (x: number, y: number): void => {
    let timer;
    if (!maze.isInArea(x, y)) throw new Error('x or y is not inside the maze')
    maze.visited![x][y] = true;
    maze.path[x][y] = true;
    maze.maze[x][y] = 1;
    setData(Array.from(maze.maze))
    if (x === maze.exitX && y === maze.exitY) return;
    for (let i = 0; i < 4; i++) {
      let newX = x + direction[i][0];
      let newY = y + direction[i][1];
      if (maze.isInArea(newX, newY) && maze.getCell(newX, newY) === MazeData.ROAD && !maze.visited![newX][newY]) {
       setTimeout(()=>go(newX, newY),50);
      }
    }
    if(timer) clearTimeout(timer);
  }
  return (
    <div className="container">
      <div className="wrapper">
        {
          data.map((item, index) =>
            // to remove the gap between divs
            <div style={{ fontSize: 0 }}>{
              item.map(i => {
                return <div className={classNames('cell', {
                  'wall': i === '#',
                  'path': i === 1,
                  'road': i === ' ',
                })} />
              }
              )
            }</div>
          )
        }
      </div>
      <button onClick={handleClick}>Solve</button>
    </div>
  );
}

为了一次只渲染一次路径,我添加了setTimeout使其起作用。否则,将在不显示每个步骤的情况下呈现路径。但是,我发现完成整个渲染需要永远的时间。我只是想知道是否是导致问题或其他问题的setTimeout。如果是这样,是否有任何其他方法可以解决该问题,从而延迟了递归。

1 个答案:

答案 0 :(得分:0)

通过添加setTimeout,您确实改变了算法。代替了最初的深度优先搜索,它已经变成了广度优先搜索。

遍历邻居时,您会调用多个setTimeout调用,这些调用将并行挂起。

由于使用go安排了setTimeout的调用,而没有对该单元格首先将visited设置为true,因此您将遇到go被安排在其上运行的情况相同会多次协调。

还要注意,您永远不会为timer分配一个值,从而使clearTimeout不使用。该计划也是不可能的,因为您正在同时计划多个超时。因此,即使在找到目标之后,许多计时器仍将继续超时并仍然调用go

对于引入暂停,我建议将go转换为async函数,并对基于await的Promise进行setTimeout。您可能需要远离递归,而应实施一个迭代解决方案,该解决方案可能基于堆栈(因此搜索顺序仍为DFS)。

这是看起来如何:

// Make function async:
const go = async (x: number, y: number): void => {
    if (!maze.isInArea(x, y)) throw new Error('x or y is not inside the maze')
    let stack = [[x, y]]; // A stack to replace the use of recursion
    while (stack.length) {
        let [x, y] = stack.pop();
        if (maze.visited![x][y]) continue; // Already visited
        maze.visited![x][y] = true;
        maze.path[x][y] = true;
        maze.maze[x][y] = 1;
        setData(Array.from(maze.maze));
        await new Promise(resolve => setTimeout(resolve, 10)); // Await 10ms
        if (x === maze.exitX && y === maze.exitY) return;
        // Reverse the direction of this loop to get the original DFS order
        for (let i = 3; i >= 0; i--) { 
            let newX = x + direction[i][0];
            let newY = y + direction[i][1];
            if (maze.isInArea(newX, newY) && maze.getCell(newX, newY) === MazeData.ROAD 
                                          && !maze.visited![newX][newY]) {
                stack.push([newX, newY]);
            }
        }
    }
}

作为setTimeout的替代方法,您还可以使用:

    await new Promise(requestAnimationFrame); // Await next paint

在每个步骤中渲染上千个div元素非常困难。完全重新设计后,每个单元都有一个单独的组件,每个单元都有自己的呈现和状态,将大大提高性能。