我正在尝试使用react钩子重新创建dijkstras寻路可视化工具。
包装器组件如下
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 = 3;
const TOTAL_COLS = 6;
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 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 } = node;
return (
<Node
key={nodeIdx}
col={col}
row={row}
isStart={isStart}
isFinish={isFinish}
isWall={isWall}
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;
};
视频教程供参考:https://www.youtube.com/watch?v=msttfIHHkak
在第一个渲染中,通过在二维数组(包括起始节点和结束节点)上进行映射生成网格。
如果单击并拖动到网格上,则将其切换为开/关,但这会导致整个网格被重新渲染两次,尽管只应重新渲染在此过程中修改的节点。
我无法弄清楚仅当传递下来的道具发生变化时才重新渲染节点。
答案 0 :(得分:2)
重新渲染的问题是,即使使用useCallback方法,实际上您是在nodeGrid更改时重新创建函数,因此无法利用Node
上React.memo的性能优化组件,这是因为您的所有onMouseDown, onMouseEnter, onMouseLeave
处理函数都已重新创建
此外,当您将mouseIsPressed用作状态时,由于这种情况,您也被迫触发重新渲染并再次创建回调。
这里的解决方案是利用状态更新回调,还使用mouseIsPressed作为参考而不是状态
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);
}, []);
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;
}, []);