[英]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.