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

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;

原因分析

  1. 组件重新挂载导致状态丢失:SVGContainer定义在App组件内部,当App的状态(比如startX/startY)更新时,SVGContainer会被重新创建,触发组件卸载再重新挂载,内部的rectangles状态会被重置为空数组,所以之前生成的矩形和生命线全部消失。
  2. 普通变量无法保持状态:dragOnGoing和y是普通变量,不是React状态,组件重新渲染时会被重置为初始值,导致交互逻辑混乱。
  3. 事件监听未正确清理: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 10:19:56