React中点击SVG生命线时其他SVG组件消失问题排查
问题:点击SVG生命线后组件消失的原因与解决办法
我正在开发一个序列图生成功能:
- 拥有3个可拖拽列表项,拖拽到SVG区域后会生成带名称和生命线的矩形
- 期望当存在2个以上对象时,点击一个对象的生命线并连接到另一个对象
- 当前异常行为:点击生命线时,其他SVG组件会消失,但
onClick函数并未修改相关对象的状态,无法理解该行为
实现代码
import React, { useState } from 'react'; const App = () => { const [startX, setStartX] = useState(null); const [startY, setStartY] = useState(null); const [endX, setEndX] = useState(null); const [endY, setEndY] = useState(null); let dragOnGoing = false; let y = null; const SidebarItem = ({ name }) => { const handleDragStart = (event) => { event.dataTransfer.setData('text/plain', name); }; return ( <li draggable onDragStart={handleDragStart}> {name} </li> ); }; const SVGContainer = () => { const [rectangles, setRectangles] = useState([]); const handleDrop = (event) => { event.preventDefault(); const itemName = event.dataTransfer.getData('text/plain'); const rect = event.target.getBoundingClientRect(); const x = event.clientX - rect.left; const y = 22; setRectangles((prevRectangles) => [ ...prevRectangles, { name: itemName, x, y }, ]); }; const handleDragOver = (event) => { event.preventDefault(); }; const handleRectangleRightClick = (index) => { setRectangles((prevRectangles) => { const updatedRectangles = [...prevRectangles]; updatedRectangles.splice(index, 1); return updatedRectangles; }); }; const handleLineClick = (event) => { if (!dragOnGoing) { // setDragOnGoing(true); setStartX(event.clientX); setStartY(event.clientY); y = event.clientY; document.addEventListener("mousemove", handleMouseMove); dragOnGoing = true; } else { document.removeEventListener("mousemove", handleMouseMove); console.log("not dragging"); setEndX(event.clientX); setEndY(y); dragOnGoing = false; return; } } const handleMouseMove = (event) => { setEndX(event.clientX); setEndY(y); }; return ( <svg onDrop={handleDrop} onDragOver={handleDragOver} width="100vw" height="500" style={{ border: '1px solid black' }} onContextMenu={(event) => event.preventDefault()} // Disable default right-click context menu > {rectangles.map((rectangle, index) => ( <g key={index}> <rect x={rectangle.x} y={rectangle.y} width="100" height="50" fill="blue" onContextMenu={() => handleRectangleRightClick(index)} // Handle right-click on rectangle /> <text x={rectangle.x + 10} y={rectangle.y + 30} fill="white" fontWeight="bold" > {rectangle.name} </text> <line style={{ cursor: 'pointer' }} strokeWidth={3} onClick={handleLineClick} x1={rectangle.x + 50} y1={rectangle.y + 50} x2={rectangle.x + 50} y2="500" stroke="black" /> </g> ))} <><line x1={startX} y1={startY} x2={endX} y2={endY} stroke="black" strokeWidth="2" ></line> <polygon // x={endX} // y={endY} points={`${endX},${endY + 5} ${endX},${endY - 5} ${endX >= startX ? endX + 5 : endX - 5},${endY}`} fill="black" stroke="black" strokeWidth="2" /> </> </svg> ); }; return ( <div> <ul> <SidebarItem name="Item 1" /> <SidebarItem name="Item 2" /> <SidebarItem name="Item 3" /> </ul> <SVGContainer /> </div> ); }; export default App;
原因分析
- 组件重新挂载导致状态丢失:
SVGContainer定义在App组件内部,当App的状态(比如startX/startY)更新时,SVGContainer会被重新创建,触发组件卸载再重新挂载,内部的rectangles状态会被重置为空数组,所以之前生成的矩形和生命线全部消失。 - 普通变量无法保持状态:
dragOnGoing和y是普通变量,不是React状态,组件重新渲染时会被重置为初始值,导致交互逻辑混乱。 - 事件监听未正确清理:
handleLineClick中添加的mousemove监听没有在组件卸载时移除,可能引发内存泄漏和异常行为。
解决办法
1. 将SVGContainer移出App组件外部
避免每次App状态更新时重新定义组件,防止不必要的挂载/卸载,同时通过props传递所需的状态和回调函数。
2. 将dragOnGoing和y改为React状态
确保状态在组件渲染过程中保持一致,避免被重置。
3. 使用useEffect管理事件监听
在组件挂载时添加监听,卸载时移除,避免内存泄漏。
修改后的代码
import React, { useState, useEffect, useCallback } from 'react'; const SidebarItem = ({ name }) => { const handleDragStart = (event) => { event.dataTransfer.setData('text/plain', name); }; return ( <li draggable onDragStart={handleDragStart}> {name} </li> ); }; const SVGContainer = ({ startX, setStartX, startY, setStartY, endX, setEndX, endY, setEndY }) => { const [rectangles, setRectangles] = useState([]); const [dragOnGoing, setDragOnGoing] = useState(false); const [lineY, setLineY] = useState(null); const handleMouseMove = useCallback((event) => { setEndX(event.clientX); setEndY(lineY); }, [setEndX, setEndY, lineY]); useEffect(() => { if (dragOnGoing) { document.addEventListener("mousemove", handleMouseMove); } else { document.removeEventListener("mousemove", handleMouseMove); } return () => { document.removeEventListener("mousemove", handleMouseMove); }; }, [dragOnGoing, handleMouseMove]); const handleDrop = (event) => { event.preventDefault(); const itemName = event.dataTransfer.getData('text/plain'); const rect = event.target.getBoundingClientRect(); const x = event.clientX - rect.left; const y = 22; setRectangles((prevRectangles) => [ ...prevRectangles, { name: itemName, x, y }, ]); }; const handleDragOver = (event) => { event.preventDefault(); }; const handleRectangleRightClick = (index) => { setRectangles((prevRectangles) => { const updatedRectangles = [...prevRectangles]; updatedRectangles.splice(index, 1); return updatedRectangles; }); }; const handleLineClick = (event) => { if (!dragOnGoing) { setStartX(event.clientX); setStartY(event.clientY); setLineY(event.clientY); setDragOnGoing(true); } else { setEndX(event.clientX); setEndY(lineY); setDragOnGoing(false); } }; return ( <svg onDrop={handleDrop} onDragOver={handleDragOver} width="100vw" height="500" style={{ border: '1px solid black' }} onContextMenu={(event) => event.preventDefault()} > {rectangles.map((rectangle, index) => ( <g key={index}> <rect x={rectangle.x} y={rectangle.y} width="100" height="50" fill="blue" onContextMenu={() => handleRectangleRightClick(index)} /> <text x={rectangle.x + 10} y={rectangle.y + 30} fill="white" fontWeight="bold" > {rectangle.name} </text> <line style={{ cursor: 'pointer' }} strokeWidth={3} onClick={handleLineClick} x1={rectangle.x + 50} y1={rectangle.y + 50} x2={rectangle.x + 50} y2="500" stroke="black" /> </g> ))} {startX !== null && ( <> <line x1={startX} y1={startY} x2={endX || startX} y2={endY || startY} stroke="black" strokeWidth="2" /> {endX !== null && ( <polygon points={`${endX},${endY + 5} ${endX},${endY - 5} ${endX >= startX ? endX + 5 : endX - 5},${endY}`} fill="black" stroke="black" strokeWidth="2" /> )} </> )} </svg> ); }; const App = () => { const [startX, setStartX] = useState(null); const [startY, setStartY] = useState(null); const [endX, setEndX] = useState(null); const [endY, setEndY] = useState(null); return ( <div> <ul> <SidebarItem name="Item 1" /> <SidebarItem name="Item 2" /> <SidebarItem name="Item 3" /> </ul> <SVGContainer startX={startX} setStartX={setStartX} startY={startY} setStartY={setStartY} endX={endX} setEndX={setEndX} endY={endY} setEndY={setEndY} /> </div> ); }; export default App;
内容的提问来源于stack exchange,提问作者Vedant Shah
相关产品推荐
相关产品推荐

