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

使用React.js读取CSV文件并通过Chart.js展示时canvas复用报错如何解决

问题排查与解决方案

错误根因

报错本质是同一个canvas画布被重复初始化了Chart实例,且没有正确销毁之前的实例,结合你提供的代码,具体错误写法如下:

  • 没有把数据请求、Chart实例初始化逻辑放在副作用钩子useEffect里,组件每一次重渲染都会触发d3.csv请求,请求成功后就会尝试初始化新的Chart实例
  • 用来存Chart实例的myChart是组件内部的普通变量,组件重渲染后变量会被重置,你根本拿不到上一次渲染生成的Chart实例,自然也没法正确调用destroy方法
  • 直接使用document.getElementById获取DOM元素,不符合React操作DOM的规范,容易拿到过期的DOM节点
  • 你已经引入了react-chartjs-2封装好的Chart组件,却选择手动实例化Chart,相当于放弃了封装层自带的实例生命周期管理能力

修复方案

方案1:改用react-chartjs-2封装组件(推荐)

直接用封装好的Line组件,不需要手动管理实例的创建和销毁,封装层已经帮你处理了生命周期:

import React, { useState, useEffect } from 'react'
import { Line } from 'react-chartjs-2'
import * as d3 from 'd3'
import {
  Chart as ChartJS,
  CategoryScale,
  LinearScale,
  PointElement,
  LineElement,
  Title,
  Tooltip,
  Legend,
} from 'chart.js'

// 注册Chart.js需要的模块
ChartJS.register(
  CategoryScale,
  LinearScale,
  PointElement,
  LineElement,
  Title,
  Tooltip,
  Legend
)

const BarChart1 = () => {
  // 存储图表数据
  const [chartData, setChartData] = useState(null)

  useEffect(() => {
    const filename = 'drug-abusers-by-gender.csv';
    d3.csv(filename).then(loadedData => {
      const labels = []
      const data = []
      // 注意:d3.csv返回的数组第一个元素已经是解析后的数据,不需要从i=1开始遍历,除非你的csv第一行是重复表头
      for (let i = 0; i < loadedData.length; i++) {
        labels.push(loadedData[i].year)
        data.push(Number(loadedData[i].no_of_drug_abusers))
      }
      setChartData({
        labels,
        datasets: [{
          data,
          fill: false,
          pointRadius: 0,
          pointHoverRadius: 0,
          borderColor: 'rgb(100,100,100)'
        }]
      })
    })
  }, []) // 空依赖数组,只在组件首次挂载时执行一次请求

  const options = {
    responsive: true,
    plugins: {
      legend: {
        display: false
      }
    }
  }

  // 数据加载完成前返回空,避免渲染空canvas
  if (!chartData) return null

  return <Line data={chartData} options={options} />
}

export default BarChart1

方案2:手动管理Chart实例(保留手动初始化写法)

如果你一定要手动操作实例,需要用useRef存实例和canvas元素,同时在组件卸载时销毁实例:

import React, { useEffect, useRef } from 'react'
import { Chart } from 'chart.js'
import * as d3 from 'd3'

const BarChart1 = () => {
  // 存canvas元素的ref
  const canvasRef = useRef(null)
  // 存Chart实例的ref,重渲染不会重置
  const chartRef = useRef(null)

  useEffect(() => {
    const filename = 'drug-abusers-by-gender.csv';
    d3.csv(filename).then(loadedData => {
      const labels = []
      const data = []
      for (let i = 0; i < loadedData.length; i++) {
        labels.push(loadedData[i].year)
        data.push(Number(loadedData[i].no_of_drug_abusers))
      }

      // 初始化前先销毁已存在的实例
      if (chartRef.current) {
        chartRef.current.destroy()
      }

      // 初始化新实例
      chartRef.current = new Chart(canvasRef.current, {
        type: 'line',
        data: {
          labels,
          datasets: [{
            data,
            fill: false,
            pointRadius: 0,
            pointHoverRadius: 0,
            borderColor: 'rgb(100,100,100)'
          }]
        }
      })
    })

    // 组件卸载时销毁实例,避免内存泄漏和重复实例报错
    return () => {
      if (chartRef.current) {
        chartRef.current.destroy()
      }
    }
  }, [])

  return <canvas ref={canvasRef}></canvas>
}

export default BarChart1

额外注意点

  • 你的代码中原来的d3.csv用了回调写法,建议改成Promise写法更易维护,也避免回调地狱
  • 从csv读取的no_of_drug_abusers是字符串类型,需要转成数字再传入Chart,否则图表可能渲染异常
  • 如果你的csv文件第一行是表头,d3.csv解析后会自动跳过表头把第一行数据作为数组第一个元素,不需要从i=1开始遍历,否则会丢失第一条数据

内容的提问来源于stack exchange,提问作者Kok Chong

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 09:45:07