簡體   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