繁体   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?

目前,我在重新创建 dijkstras 寻路可视化工具时遇到了两个问题。

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

  1. 如果单击/单击并拖动到网格上,则可以创建阻挡路径的墙节点。 但是碰巧的是,如果您单击并拖动几个节点,释放鼠标按钮并在您结束的同一节点上单击并拖动,鼠标指针会以某种方式被禁用并且不会注意到 onMouseUp 事件。 结果:鼠标仍然被点击——>因此即使没有按下鼠标,你仍然会在onMouseOver上创建墙

  2. 以前,算法访问的节点是通过getElementById.classname添加 class 来设置动画的。 但我实际上想通过传递作为 state 一部分的isVisited道具来更新子组件中的 class 。 但我无法弄清楚为什么我的isVisited中的 isVisited 在我调用setState之前会更新,或者我如何才能正确地做到这一点。 目前,所有访问过的节点在 go 变回白色之前立即进行动画处理,就好像它们没有被访问过一样。

包装器组件:

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;
};

子组件:

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;

我不确定我能否重现您解释的完全相同的错误。 但是现在您的代码可能无法正确处理鼠标上下事件。

我可以重现的内容:您在节点外单击并拖动鼠标。 如果您将鼠标带回节点中,它将表现得就像鼠标仍然处于关闭状态一样。

如果你想解决这个问题,而不是在每个单独的节点上监听鼠标,你可以使用useEffect(() => {window.addEventListener("mouseup"

对于您的第一个问题:-

在您的 handleMouseDown 代码中执行 e.preventDefault() ,如下所示:-

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

在你的 Node.jsx 中:-

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

对于第二个问题:-

我想我找到了它的根本原因。 基本上,您将grid object 传递给dijkstra's function。 您在整个算法中使用它而没有先复制它(记住对象在 JS 中作为引用传递)。 这就是isVisited变为 true 的原因,因为您的算法已修改网格。 因此,在内部,dijkstra 制作了网格的深层副本,然后从您的算法开始。 我希望这有帮助:)。

无需深入了解代码,一眼就能看出您的所有处理程序都使用空依赖项 arrays 例如

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

这意味着它只会在组件安装上重新渲染 function。 我假设您可能不希望在handleMouseEnter类的东西上使用它,因为随后的输入将具有来自先前渲染的陈旧值。 depends数组应该具有 function 需要的任何值,例如prevNodeGrid

除非您只希望它们在组件安装时运行一次,否则您的某些useEffects可能也是如此。

由于网格是一个包含其他 arrays 的数组,因此如果将其传递给 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