map()函数中同一元素多次重渲染问题排查
问题原因及解决方法
核心问题1:用Math.random()作为React元素的key
React靠key识别列表元素的唯一性,每次渲染时Math.random()都会生成新值,这会让React误以为列表里的所有元素都是全新的,每次渲染都会销毁旧元素、重建新元素,直接表现就是同一元素多次进入遍历逻辑,出现重复渲染。
解决方法:
给每个arrow对象添加唯一标识(比如时间戳+随机数),用这个标识作为key:
// 新增箭头时添加唯一id setArrowsArray(prev => [...prev, { id: Date.now() + Math.floor(Math.random() * 1000), startX: s_x, startY: y, endX: event.clientX, endY: y, }]); // 渲染时用id作为key {arrowsArray.length>=1 && arrowsArray.map((arrow) => { return( <div key={arrow.id}> {/* ... 原有line、polygon代码 ... */} </div>); })}
核心问题2:直接修改state数组,违反React不可变原则
你这段代码直接拿state数组的引用修改:
let arrow = arrowsArray; arrow.push({/* ... */}); setArrowsArray(arrow);
arrowsArray是React state的引用,直接push修改后数组的内存地址没变,React会判定state没有更新,进而引发渲染逻辑异常,比如旧元素重复渲染、新元素不生效等。
解决方法:
创建数组副本再修改,用扩展运算符或者slice()实现:
// 正确写法:基于旧state创建新数组 setArrowsArray(prev => [...prev, { id: Date.now() + Math.floor(Math.random() * 1000), startX: s_x, startY: y, endX: event.clientX, endY: y, }]);
核心问题3:dragOnGoing用普通变量,存在闭包陷阱
你在useEffect里用普通变量dragOnGoing,但useEffect依赖是空数组,它捕获的是初始值false,后续手动修改dragOnGoing = true/false不会被React追踪,而且组件每次渲染都会重新声明这个变量,导致逻辑混乱,间接引发渲染异常。
解决方法:
把dragOnGoing改成React state,同时把相关依赖项加入useEffect的依赖数组:
// 先声明state const [dragOnGoing, setDragOnGoing] = useState(false); const [s_x, setSx] = useState(0); const [y, setY] = useState(0); // useEffect里修改state const handleMouseClick = (event) => { if (!dragOnGoing) { setDragOnGoing(true); setStartX(event.clientX); setStartY(event.clientY); setSx(event.clientX); setY(event.clientY); document.addEventListener("mousemove", handleMouseMove); } else { document.removeEventListener("mousemove", handleMouseMove); setEndX(event.clientX); setEndY(y); setArrowsArray(prev => [...prev, { id: Date.now() + Math.floor(Math.random() * 1000), startX: s_x, startY: y, endX: event.clientX, endY: y, }]); setDragOnGoing(false); } }; // useEffect依赖数组添加相关变量 useEffect(() => { // ... handleMouseMove、handleMouseClick代码 ... document.addEventListener("click", handleMouseClick); return () => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("click", handleMouseClick); }; }, [dragOnGoing, s_x, y]);
修复后关键代码示例
渲染部分
{arrowsArray.length >= 1 && arrowsArray.map((arrow) => { return ( <div key={arrow.id}> <line x1={arrow.startX} y1={arrow.startY} x2={arrow.endX} y2={arrow.endY} stroke="black" strokeWidth="2" ></line> <polygon points={`${arrow.endX},${arrow.endY + 5} ${arrow.endX},${arrow.endY - 5} ${ arrow.endX >= arrow.startX ? arrow.endX + 5 : arrow.endX - 5 },${arrow.endY}`} fill="black" stroke="black" strokeWidth="2" /> </div> ); })}
事件监听部分
const [dragOnGoing, setDragOnGoing] = useState(false); const [s_x, setSx] = useState(0); const [y, setY] = useState(0); const [startX, setStartX] = useState(0); const [startY, setStartY] = useState(0); const [endX, setEndX] = useState(0); const [endY, setEndY] = useState(0); const [arrowsArray, setArrowsArray] = useState([]); useEffect(() => { const handleMouseMove = (event) => { setEndX(event.clientX); setEndY(y); }; const handleMouseClick = (event) => { if (!dragOnGoing) { setDragOnGoing(true); setStartX(event.clientX); setStartY(event.clientY); setSx(event.clientX); setY(event.clientY); document.addEventListener("mousemove", handleMouseMove); } else { document.removeEventListener("mousemove", handleMouseMove); setEndX(event.clientX); setEndY(y); setArrowsArray(prev => [...prev, { id: Date.now() + Math.floor(Math.random() * 1000), startX: s_x, startY: y, endX: event.clientX, endY: y, }]); setDragOnGoing(false); } }; document.addEventListener("click", handleMouseClick); return () => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("click", handleMouseClick); }; }, [dragOnGoing, s_x, y]);
内容的提问来源于stack exchange,提问作者Mansi V Jain
相关产品推荐
相关产品推荐

