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

React useEffect触发updateChart执行两次致图表数据重复如何解决

问题根因

初始化阶段函数执行两次是两个问题叠加导致的:

  • React 18 开发环境默认开启严格模式,首次加载组件时会主动执行一轮「挂载→卸载→重挂载」流程,检测副作用写法是否规范,这会让依赖项为初始值的useEffect执行两次。
  • 你的代码本身存在副作用设计漏洞:
    • 图表配置data被定义在组件外部,属于模块级共享变量,所有组件实例操作的都是同一份数据引用
    • updateChart逻辑是直接往原有数据集里push新值,执行前从不清空旧数据,只要函数重复调用必然会重复追加相同数据。就算没有严格模式的双调用,后续userData.transactions更新时,你也会把已经存在的旧交易数据重复推入数组,一样会出现数据重复问题。
修复方法

不需要关闭严格模式,只要把数据更新逻辑改成「全量生成新数据」而非「累加旧数据」,就能保证副作用不管执行多少次,最终结果都一致:

  1. 把图表数据移入组件内部维护,不要放在组件外作为全局变量
  2. 重写数据更新逻辑,每次执行时直接根据最新的userData.transactions生成完整数据集,替换原有内容而非追加
  3. 保证同一份输入下,副作用多次执行的输出结果完全一致

修复后的完整组件代码:

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

ChartJS.register(
  CategoryScale,
  LinearScale,
  PointElement,
  LineElement,
  Title,
  Tooltip,
  Legend,
  Filler
);

const options = {
  responsive: true,
  plugins: {
    legend: {
      position: 'top',
    },
    title: {
      display: true,
      text: 'Chart.js Line Chart',
    },
  },
};

const labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July'];

const ChartHistory = ({userData}) => {
  // 把图表数据移到组件内部维护
  const [chartData, setChartData] = useState({
    labels,
    datasets: [
      {
        fill: true,
        label: 'Dataset 1',
        data: [],
        borderColor: 'rgb(53, 162, 235)',
        backgroundColor: 'rgba(53, 162, 235, 0.5)',
      },
    ],
  });

  useEffect(() => {
    // 每次更新直接生成新的data数组,不做push累加,从根源避免重复
    setChartData(prev => ({
      ...prev,
      datasets: prev.datasets.map((dataset, idx) => {
        if (idx !== 0) return dataset;
        return {
          ...dataset,
          // 直接用transactions生成新数组,完全替换旧值
          data: userData.transactions.map(item => item.amount)
        }
      })
    }))
  }, [userData.transactions])    

  return (
    <div>
      <Line options={options} data={chartData} />
    </div>
  )
}

export default ChartHistory

更优实现:图表数据完全是从userData.transactions派生的值,根本不需要用useEffect手动维护,直接用useMemo派生即可,代码更简洁,也不会有副作用重复执行的问题:

const chartData = useMemo(() => ({
  labels,
  datasets: [
    {
      fill: true,
      label: 'Dataset 1',
      data: userData.transactions.map(item => item.amount),
      borderColor: 'rgb(53, 162, 235)',
      backgroundColor: 'rgba(53, 162, 235, 0.5)',
    },
  ],
}), [userData.transactions])

这种写法完全没有手动操作副作用的逻辑,不管组件重渲染多少次,只要userData.transactions不变,生成的图表数据就完全一致,不会出现重复值。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 11:21:43