You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 18:23:20