React中更新拖拽节点状态导致应用崩溃问题求助
问题
我正在开发一款搜索算法可视化工具,Grid组件初始化矩阵并渲染为网格,每个网格单元对应一个Node组件。需求是允许用户拖拽start和end节点到任意单元格,目前通过onDragStart和onDragEnter事件实现以下逻辑:
- 追踪用户拖拽的节点类型(start/end);
- 通过
onDragStart记录节点原所在单元格; - 通过
onDragEnter追踪节点当前悬停的单元格; - 将节点追加到目标单元格;
- 更新矩阵中原节点和新节点的状态;
- 需要更新Grid组件中start/end节点的行列状态,供后续dijkstra函数使用。
但更新该状态时应用崩溃,报错:DOMException: Node.removeChild: The node to be removed is not a child of this node。注释掉状态更新代码则功能正常,以下是Node组件代码:
const Node = ({ nodeMatrix, stateHandling, dragStateHandling, row, col, isStart, isEnd }) => { const { setStartNodeRow, setStartNodeCol, setEndNodeRow, setEndNodeCol } = stateHandling; const { draggedItem, setDraggedItem } = dragStateHandling; //Just handling the 'start' node for now const handleDragStart = (e) => { const isStartNode = e.target.classList.contains('start'); const startNode = document.querySelector('.start'); //const endNode = document.querySelector('.end'); const [prevStartParentNodeRow, prevStartParentNodeCol] = startNode.parentElement.id.match(/\d+/g); //const [prevEndParentNodeRow, prevEndParentNodeCol] = endNode.parentElement.id.match(/\d+/g); if(isStartNode) { setDraggedItem('start'); //Sets the data for the drag object to be retrieved later and //used to update the corresponding node in the matrix; e.dataTransfer.setData('prevNodeId', `node-${prevStartParentNodeRow}-${prevStartParentNodeCol}`); } else { setDraggedItem('end'); //e.dataTransfer.setData('prevNodeId', `node-${prevEndParentNodeRow}-${prevEndParentNodeCol}`) } }; const handleDragEnter = (e) => { const startNode = document.querySelector('.start'); const endNode = document.querySelector('.end'); //This check avoids the 'start' and 'end' divs and //always selects the parent cell; const newParent = e.target.id ? e.target : e.target.parentElement.id ? e.target.parentElement : e.target.parentElement.parentElement; const hoveringOver = newParent.id; //Retrieves the data set in 'handleDragStart' from the drag object to use as keys //to update the previous 'start' node in the grid; const [prevNodeRow, prevNodeCol] = e.dataTransfer.getData('prevNodeId').match(/\d+/g); //Gets the id of the cell we're currently hovering over to update //the new 'start' node in the grid; const [newRow, newCol] = hoveringOver.match(/\d+/g); if(draggedItem === 'start') { //All this methods work as intended, //the previous 'start' node gets updated alongside the new one //in the nodesMatrix; newParent.appendChild(startNode); nodeMatrix[prevNodeRow][prevNodeCol].isStart = false; nodeMatrix[newRow][newCol].isStart = true; //↓ If uncommented, this throws the following error //↓ as soon as we drag the start node: //(DOMException: Node.removeChild: The node to be removed is not a child of this node); /* setStartNodeRow(parseInt(newRow)); setStartNodeCol(parseInt(newCol)); */ }; if(draggedItem === 'end') { newParent.appendChild(endNode); nodeMatrix[prevNodeRow][prevNodeCol].isEnd = false; nodeMatrix[newRow][newCol].isEnd = true; /* setEndNodeRow(parseInt(newRow)); setEndNodeCol(parseInt(newCol)); */ }; }; return ( <div id={`node-${ row }-${ col }`} onDragEnter={handleDragEnter} className={...} > { isStart && <div onDragStart={handleDragStart} draggable={true} className={...} > <i className={...}/> </div> } { isEnd && <div onDragStart={handleDragStart} draggable={true} className={...} > <i className={...}/> </div> } </div> ) } export default Node
解决方案
问题根源
报错是因为手动操作DOM与React状态更新的冲突:
你通过newParent.appendChild(startNode)把start节点的DOM元素手动移到了新单元格,但调用setStartNodeRow/setStartNodeCol会触发Grid组件重新渲染。此时React会尝试移除旧单元格里的start节点DOM,但该节点已经被你移走了,React找不到目标节点,就抛出了Node.removeChild错误。
修复步骤
- 放弃手动DOM操作:React是状态驱动视图,所有DOM变化应该由状态更新触发,不要用
appendChild这类原生DOM方法。 - 只维护状态作为唯一数据源:拖拽时仅更新
nodeMatrix的节点状态和Grid的start/end行列状态,让React自动负责DOM渲染。 - 让React自动渲染节点:Node组件已经根据
isStart/isEnd属性渲染对应节点,状态更新后React会自动把节点放到正确的单元格中。
修改后的关键代码
调整handleDragEnter函数,移除手动DOM操作,仅更新状态:
const handleDragEnter = (e) => { // 获取原节点行列 const [prevNodeRow, prevNodeCol] = e.dataTransfer.getData('prevNodeId').match(/\d+/g); // 获取目标单元格 const newParent = e.target.id ? e.target : e.target.parentElement.id ? e.target.parentElement : e.target.parentElement.parentElement; const [newRow, newCol] = newParent.id.match(/\d+/g); // 避免拖拽到原位置时重复更新 if (prevNodeRow === newRow && prevNodeCol === newCol) return; if(draggedItem === 'start') { // 更新矩阵状态(注意:若nodeMatrix是不可变对象,需深拷贝后修改) nodeMatrix[prevNodeRow][prevNodeCol].isStart = false; nodeMatrix[newRow][newCol].isStart = true; // 更新Grid的start节点状态 setStartNodeRow(parseInt(newRow)); setStartNodeCol(parseInt(newCol)); }; if(draggedItem === 'end') { nodeMatrix[prevNodeRow][prevNodeCol].isEnd = false; nodeMatrix[newRow][newCol].isEnd = true; setEndNodeRow(parseInt(newRow)); setEndNodeCol(parseInt(newCol)); }; };
额外优化建议
- 确保矩阵状态的不可变性:如果直接修改
nodeMatrix的元素,React可能检测不到变化导致不渲染。建议创建矩阵的深拷贝再修改,或使用immer工具简化不可变更新。 - 优化拖拽事件触发频率:可以在
handleDragEnter中添加防抖逻辑,避免快速拖拽时频繁触发状态更新。
内容的提问来源于stack exchange,提问作者LeoM
相关产品推荐
相关产品推荐

