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