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

React中使用D3加载数据时生成多个重复SVG元素问题求助

问题原因

你的判断方向基本正确,核心错误是把D3操作真实DOM的逻辑直接写在了组件渲染主流程里,既没控制执行时机,也没做重复内容清理,重复SVG的触发逻辑很明确:

  • 组件首次加载时data初始值为null,直接返回加载中的<pre>标签,此时页面根本不存在id为Wrapper的容器,D3选不到元素自然不会插入SVG
  • 接口请求完成后调用setData触发重渲染,组件不再走加载分支,你写的选容器、追加SVG的逻辑开始执行,往Wrapper里插入第一个SVG
  • 如果你用的是React 18+开发环境,默认开启的严格模式会自动执行一次挂载→卸载→重挂载流程;后续只要有任何触发组件重渲染的动作(比如父组件更新、其他state变动),都会再次执行组件函数里的D3追加逻辑。而D3的append()方法是往选中元素里新增内容,不会自动清空已有节点,每重渲染一次就多插一个SVG,自然会出现多个重复容器。
  • 另外你现在的代码还有个时序隐患:D3选择#Wrapper的逻辑写在return语句之前,这时候React还没把本次渲染生成的Wrapper div提交到真实DOM上,第一次拿到数据渲染时D3其实选不到元素,是靠后续的重渲染才把SVG加上的,属于典型的DOM操作和渲染生命周期不同步问题。
修复方法
  • 所有涉及真实DOM操作的D3绘图逻辑,必须放到useEffect钩子中执行,依赖项设为data,保证只有DOM挂载完成、数据就绪后才执行绘图
  • 每次执行绘图逻辑前,先清空容器内的旧内容,从根源避免重复追加节点
  • 推荐用useRef获取DOM容器引用,代替id选择器,避免和页面其他同名id冲突,也更符合React的开发范式
  • 可以在绘图相关的useEffect中返回清理函数,组件卸载时清空DOM、移除事件监听,避免内存泄漏

修复后的完整代码如下:

import React, { useState, useEffect, useRef } from 'react';
import * as d3 from 'd3';

const LineChart = () => {
  const weatherDataUrl =
    'https://gist.githubusercontent.com/anujshaanmydash/17687a309a87105ab8991a3dcfd8c7e3/raw/weatherData.json';
  const wrapperRef = useRef(null);
  const [data, setData] = useState(null);

  // 单独处理数据拉取
  useEffect(() => {
    d3.json(weatherDataUrl).then(setData);
  }, []);

  // 单独处理D3绘图,依赖数据和DOM引用
  useEffect(() => {
    // 数据或DOM没准备好时直接返回
    if (!data || !wrapperRef.current) return;

    const yAccessor = (d) => d.temperatureMax;
    const dateParser = d3.timeParse('%Y-%m-%d');
    const xAccessor = (d) => dateParser(d.date);

    const dimensions = {
      width: 1200,
      height: 500,
      margins: {
        top: 15,
        right: 15,
        bottom: 30,
        left: 50,
      },
    };
    dimensions.boundedWidth =
      dimensions.width - dimensions.margins.left - dimensions.margins.right;
    dimensions.boundedHeight =
      dimensions.height - dimensions.margins.top - dimensions.margins.bottom;

    // 核心:每次绘图前清空容器内所有旧节点,避免重复生成SVG
    const wrapperEl = d3.select(wrapperRef.current);
    wrapperEl.selectAll('*').remove();

    const wrapper = wrapperEl
      .append('svg')
      .attr('width', dimensions.width)
      .attr('height', dimensions.height);

    const bounds = wrapper
      .append('g')
      .style(
        'transform',
        `translate(${dimensions.margins.left}px,${dimensions.margins.top}px)`
      );

    // 生成比例尺
    const yScale = d3
      .scaleLinear()
      .domain(d3.extent(data, yAccessor))
      .range([dimensions.boundedHeight, 0]);

    const freezingTempArea = yScale(32);
    bounds
      .append('rect')
      .attr('x', 0)
      .attr('width', dimensions.boundedWidth)
      .attr('y', freezingTempArea)
      .attr('height', dimensions.boundedHeight - freezingTempArea)
      .attr('fill', '#e0f3f3');

    const xScale = d3
      .scaleTime()
      .domain(d3.extent(data, xAccessor))
      .range(0, dimensions.boundedWidth);

    // 后续折线、坐标轴、交互逻辑都可以在这里继续写
  }, [data]);

  if (!data) {
    return <pre>Loading....</pre>;
  }

  return <div ref={wrapperRef}></div>;
};

export default LineChart;

内容的提问来源于stack exchange,提问作者Anuj Shaan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 21:39:16