简体   繁体   English

为什么在我使用 React Hooks 调用 setState 之前,我的 state 会更新,我该如何修复禁用的鼠标指针?

[英]Why is my state beeing updated before i call setState with React Hooks and how do i fix the disabled mouse pointer?

I have two problems at the moment with recreating a dijkstras pathfinding visualizer.目前,我在重新创建 dijkstras 寻路可视化工具时遇到了两个问题。

My Codesandbox: https://codesandbox.io/s/silent-morning-t84e0我的 Codesandbox: https://codesandbox.io/s/silent-morning-t84e0

  1. If you click/ click and drag onto the grid you can create wall-nodes that block the path.如果单击/单击并拖动到网格上,则可以创建阻挡路径的墙节点。 But it happens that, if you click and drag for a few nodes, release the mouse button and click and drag on the same node you ended on, the mouse pointer is somehow disabled and doesnt notice the onMouseUp event.但是碰巧的是,如果您单击并拖动几个节点,释放鼠标按钮并在您结束的同一节点上单击并拖动,鼠标指针会以某种方式被禁用并且不会注意到 onMouseUp 事件。 result: the mouse is still clicked --> thus you still create walls onMouseOver even if the mouse is not pressed结果:鼠标仍然被点击——>因此即使没有按下鼠标,你仍然会在onMouseOver上创建墙

  2. Previously the nodes that are visited by the algorithm were animated by adding a class via getElementById.classname .以前,算法访问的节点是通过getElementById.classname添加 class 来设置动画的。 But i actually want to update the class in the child component by passing down the isVisited prop that is part of the state anyway.但我实际上想通过传递作为 state 一部分的isVisited道具来更新子组件中的 class 。 But i cant figure out why my isVisited in my state is updated before i call setState or how i can do it properly.但我无法弄清楚为什么我的isVisited中的 isVisited 在我调用setState之前会更新,或者我如何才能正确地做到这一点。 Currently the all the visited nodes are animated at once before they go back to white as if they were not visited.目前,所有访问过的节点在 go 变回白色之前立即进行动画处理,就好像它们没有被访问过一样。

Wrapper Component:包装器组件:

import React, { useState, useEffect, useCallback, useRef } from "react";
import Node from "../Node/Node";

import "./PathfindingVisualizer.css";

import { dijkstra, getNodesInShortestPathOrder } from "../algorithms/dijkstras";

const START_NODE_ROW = 0;
const START_NODE_COL = 0;
const FINISH_NODE_ROW = 0;
const FINISH_NODE_COL = 3;

const TOTAL_ROWS = 5;
const TOTAL_COLS = 10;

const PathfindingVisualizer = () => {
  const [nodeGrid, setNodeGrid] = useState({
    grid: []
  });

  const mouseIsPressed = useRef(false);

  useEffect(() => {
    const grid1 = getInitialGrid();
    setNodeGrid({ ...nodeGrid, grid: grid1 });
  }, []);

  const handleMouseDown = useCallback((row, col) => {
    //console.log(newGrid);
    setNodeGrid(prevGrid => ({
      grid: getNewGridWithWallToggled(prevGrid.grid, row, col)
    }));
    mouseIsPressed.current = true;
    //console.log(nodeGrid);
  }, []);

  // function handleMouseDown(row, col) {
  //   const newGrid = getNewGridWithWallToggled(nodeGrid.grid, row, col);
  //  console.log(newGrid);
  //   setNodeGrid({...nodeGrid, nodeGrid[row][col]= newGrid});
  // }

  const handleMouseEnter = useCallback((row, col) => {
    //console.log(mouseIsPressed);
    if (mouseIsPressed.current) {
      setNodeGrid(prevNodeGrid => ({
        ...prevNodeGrid,
        grid: getNewGridWithWallToggled(prevNodeGrid.grid, row, col)
      }));
    }
  }, []);

  const handleMouseUp = useCallback(() => {
    mouseIsPressed.current = false;
  }, []);

  // const animateDijkstra = (visitedNodesInOrder, nodesInShortestPathOrder) => {
  //   for (let i = 0; i <= visitedNodesInOrder.length; i++) {
  //     if (i === visitedNodesInOrder.length) {
  //       setTimeout(() => {
  //         animateShortestPath(nodesInShortestPathOrder);
  //       }, 10 * i);
  //       return;
  //     }
  //     setTimeout(() => {
  //       const node = visitedNodesInOrder[i];
  //       document.getElementById(`node-${node.row}-${node.col}`).className =
  //         "node node-visited";
  //     }, 10 * i);
  //   }
  // };

  const animateDijkstra = (visitedNodesInOrder, nodesInShortestPathOrder) => {
    for (let i = 0; i <= visitedNodesInOrder.length; i++) {
      if (i === visitedNodesInOrder.length) {
        setTimeout(() => {
          animateShortestPath(nodesInShortestPathOrder);
        }, 17 * i);
        return;
      }
      setTimeout(() => {
        const node = visitedNodesInOrder[i];
        console.log("node", node);
        console.log("state", nodeGrid);
        console.log(
          "before setNode",
          nodeGrid.grid[node.row][node.col].isVisited
        );
        setNodeGrid(prevNodeGrid => ({
          ...prevNodeGrid,
          grid: getNewGridWithVisited(prevNodeGrid.grid, node.row, node.col)
          //   //grid: node
        }));
        console.log(
          "after setNode;",
          nodeGrid.grid[node.row][node.col].isVisited
        );
      }, 17 * i);
    }
  };

  const animateShortestPath = nodesInShortestPathOrder => {
    for (let i = 0; i < nodesInShortestPathOrder.length; i++) {
      setTimeout(() => {
        const node = nodesInShortestPathOrder[i];
        document.getElementById(`node-${node.row}-${node.col}`).className =
          "node node-shortest-path";
      }, 50 * i);
    }
  };

  const visualizeDijkstra = () => {
    const grid = nodeGrid.grid;
    console.log(grid);
    const startNode = grid[START_NODE_ROW][START_NODE_COL];
    const finishNode = grid[FINISH_NODE_ROW][FINISH_NODE_COL];
    const visitedNodesInOrder = dijkstra(grid, startNode, finishNode);
    const nodesInShortestPathOrder = getNodesInShortestPathOrder(finishNode);
    animateDijkstra(visitedNodesInOrder, nodesInShortestPathOrder);
  };

  //console.log(nodeGrid.grid);
  //console.log(visualizeDijkstra());
  return (
    <>
      <button onClick={visualizeDijkstra}>
        Visualize Dijkstra´s Algorithm
      </button>
      <div className="grid">
        test
        {nodeGrid.grid.map((row, rowIdx) => {
          return (
            <div className="row" key={rowIdx}>
              {row.map((node, nodeIdx) => {
                const { row, col, isStart, isFinish, isWall, isVisited } = node;
                return (
                  <Node
                    key={nodeIdx}
                    col={col}
                    row={row}
                    isStart={isStart}
                    isFinish={isFinish}
                    isWall={isWall}
                    isVisited={isVisited}
                    onMouseDown={handleMouseDown}
                    onMouseEnter={handleMouseEnter}
                    onMouseUp={handleMouseUp}
                  />
                );
              })}
            </div>
          );
        })}
      </div>
    </>
  );
};

export default PathfindingVisualizer;

//----------------------------------------------------------

const getInitialGrid = () => {
  const grid = [];
  for (let row = 0; row < TOTAL_ROWS; row++) {
    const currentRow = [];
    for (let col = 0; col < TOTAL_COLS; col++) {
      currentRow.push(createNode(col, row));
    }
    grid.push(currentRow);
  }
  return grid;
};

const createNode = (col, row) => {
  return {
    col,
    row,
    isStart: row === START_NODE_ROW && col === START_NODE_COL,
    isFinish: row === FINISH_NODE_ROW && col === FINISH_NODE_COL,
    distance: Infinity,
    isVisited: false,
    isWall: false,
    previousNode: null
  };
};

const getNewGridWithWallToggled = (grid, row, col) => {
  const newGrid = grid.slice();
  const node = newGrid[row][col];
  const newNode = {
    ...node,
    isWall: !node.isWall
  };
  newGrid[row][col] = newNode;
  return newGrid;
};

const getNewGridWithVisited = (grid, row, col) => {
  const newGrid = grid.slice();
  const node1 = newGrid[row][col];
  const newNode = {
    ...node1,
    isVisited: true
    //isVisited: !node1.isVisited
  };
  //console.log(newNode);
  newGrid[row][col] = newNode;
  return newGrid;
};

Child Component:子组件:

import React from "react";

import "./Node.css";
import { useCountRenders } from "../Node/useCountRenders";

const Node = React.memo(
  ({
    col,
    isFinish,
    isStart,
    isWall,
    onMouseDown,
    onMouseEnter,
    onMouseUp,
    row,
    isVisited
  }) => {
    //console.log("props: col, isWall, row;", col, isWall, row);
    const extraClassName = isFinish
      ? "node-finish"
      : isStart
      ? "node-start"
      : isWall
      ? "node-wall"
      : isVisited
      ? 'node-visited'
      : "";

    useCountRenders();

    console.log("node rerendered: row:", row, "col:", col);
    return (
      <div
        id={`node-${row}-${col}`}
        className={`node ${extraClassName}`}
        onMouseDown={() => onMouseDown(row, col)}
        onMouseEnter={() => onMouseEnter(row, col)}
        onMouseUp={() => onMouseUp()}
      />
    );
  }
);

export default Node;

I am not sure I can reproduce the exact same bug that you explain.我不确定我能否重现您解释的完全相同的错误。 But right now your code is maybe not handling correctly the mouse up and down event.但是现在您的代码可能无法正确处理鼠标上下事件。

What I could reproduce: you click drag and mouse up outside of the node.我可以重现的内容:您在节点外单击并拖动鼠标。 If you bring back the mouse in the node it will act like the mouse is still down.如果您将鼠标带回节点中,它将表现得就像鼠标仍然处于关闭状态一样。

If you want to fix that, rather than listen on each separate node for mouse up, you can use an useEffect(() => { with window.addEventListener("mouseup"如果你想解决这个问题,而不是在每个单独的节点上监听鼠标,你可以使用useEffect(() => {window.addEventListener("mouseup"

For your first problem:-对于您的第一个问题:-

Do a e.preventDefault() in your handleMouseDown code like so:-在您的 handleMouseDown 代码中执行 e.preventDefault() ,如下所示:-

const handleMouseDown = useCallback((e, row, col) => { //your logic e.preventDefault(); }, []);

And in your Node.jsx:-在你的 Node.jsx 中:-

onMouseDown={(e) => onMouseDown(e, row, col)}

For the second problem:-对于第二个问题:-

I think I found the root cause of it.我想我找到了它的根本原因。 Basically you're passing grid object to dijkstra's function.基本上,您将grid object 传递给dijkstra's function。 You are using it in your whole algorithm without making a copy of it first (Remember objects are passed as references in JS).您在整个算法中使用它而没有先复制它(记住对象在 JS 中作为引用传递)。 That's why isVisited becomes true because your algorithm has modified grid.这就是isVisited变为 true 的原因,因为您的算法已修改网格。 So inside, dijkstra's make a deep copy of grid and then start with your algo.因此,在内部,dijkstra 制作了网格的深层副本,然后从您的算法开始。 I hope this helps:).我希望这有帮助:)。

From an immediate glance without going through the code in depth it looks like all of your handlers use empty dependency arrays eg无需深入了解代码,一眼就能看出您的所有处理程序都使用空依赖项 arrays 例如

  const handleMouseEnter = useCallback((row, col) => {
    //console.log(mouseIsPressed);
    if (mouseIsPressed.current) {
      setNodeGrid(prevNodeGrid => ({
        ...prevNodeGrid,
        grid: getNewGridWithWallToggled(prevNodeGrid.grid, row, col)
      }));
    }
  }, []); <= empty dependency array

Meaning it will only ever re-render the function on component mount.这意味着它只会在组件安装上重新渲染 function。 I'm assuming you probably don't want that on something like a handleMouseEnter as subsequent enters it will have stale values from previous render.我假设您可能不希望在handleMouseEnter类的东西上使用它,因为随后的输入将具有来自先前渲染的陈旧值。 The dependency array should have any values the function needs depends on such as prevNodeGrid for example. depends数组应该具有 function 需要的任何值,例如prevNodeGrid

Same possibly goes for some of your useEffects unless you only want them running once on component mount.除非您只希望它们在组件安装时运行一次,否则您的某些useEffects可能也是如此。

Because the grid is an array that contains other arrays, a deep copy is required if it is passed to a function and modified in the process.由于网格是一个包含其他 arrays 的数组,因此如果将其传递给 function 并在此过程中进行修改,则需要进行深拷贝。 Otherwise changes that are made to the nested data will also change the original object/array that was passed.否则,对嵌套数据所做的更改也会更改传递的原始对象/数组。

I used lodash to to create a deep-copy of the nodeGrid.grid -state before it is passed to the dijkstras function.在传递给 dijkstras function 之前,我使用 lodash 创建了nodeGrid.grid状态的深层副本。

import _ from "lodash";

//--------

 const visualizeDijkstra = () => {

    const grid = _.cloneDeep(nodeGrid.grid);

    // other code
  }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM