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

如何在React状态更新时同步刷新Chart.js图表自定义标签

问题根因

你当前实现中自定义插件捕获的month是组件首次渲染时的闭包值,react-chartjs-2默认不会在普通props更新时重新初始化插件、刷新插件内的闭包引用,因此哪怕month prop发生变化,插件内部拿到的始终是初始值January。
价格文本可以正常更新,是因为你直接从chart.config.data上读取了图表实例持有的最新数据集,没有依赖外部闭包变量。

正确实现方案

方案1:通过chart配置传递动态值(推荐,性能最优)

将自定义插件定义移到组件外部避免重复创建,所有需要动态更新的内容都挂载到图表的options配置项上,插件直接从chart实例读取最新值,在prop变化时主动调用图表的update方法触发重绘即可,完全规避闭包陷阱。
代码示例:

import { useEffect, useRef } from 'react';
import { Chart as ChartJS, Doughnut } from 'react-chartjs-2';

// 插件定义放在组件外,仅初始化一次,不依赖组件内变量
const centerTextPlugin = {
  id: 'centerText',
  beforeDraw: (chart: any) => {
    const { width, height, ctx } = chart;
    ctx.restore();
    
    // 所有动态值从chart实例配置上读取
    const price = chart.config.data.datasets[0].data[0];
    const displayMonth = chart.options.plugins.centerText?.month;

    // 绘制中心价格文本
    let fontSize = Number((height / 200).toFixed(2));
    ctx.font = `${fontSize}em sans-serif`;
    ctx.textBaseline = "top";
    const priceText = `$${price.toFixed(2)}`;
    let textX = Math.round((width - ctx.measureText(priceText).width) / 2);
    let textY = height / 2;
    ctx.fillText(priceText, textX, textY);
    ctx.save();

    // 绘制中心月份文本
    fontSize = Number((height / 400).toFixed(2));
    ctx.font = `${fontSize}em sans-serif`;
    ctx.textBaseline = "top";
    const monthText = `${displayMonth}`;
    textX = Math.round((width - ctx.measureText(monthText).width) / 2);
    textY = height / 2 + 50;
    ctx.fillText(monthText, textX, textY);
    ctx.save();
  }
};

// 全局注册一次插件即可,无需每次渲染传入组件
ChartJS.register(centerTextPlugin);

export default function DoughnutChart({ month, data, options }) {
  const chartRef = useRef<ChartJS>(null);

  // 监听month变化,更新图表配置并重绘
  useEffect(() => {
    if (!chartRef.current) return;
    chartRef.current.options.plugins = {
      ...chartRef.current.options.plugins,
      centerText: {
        month
      }
    };
    chartRef.current.update();
  }, [month]);

  return <Doughnut
    ref={chartRef}
    options={options}
    data={data}
  />;
}

方案2:通过key强制图表重建(实现简单,适合低更新频率场景)

如果不想调整插件逻辑,可以给Doughnut组件传入随month变化的key属性,当month更新时React会直接销毁旧图表实例、重新挂载新实例,此时插件会重新捕获最新的month值:

return <Doughnut
  key={month} // key变化时组件会完全重初始化
  ref={chartRef}
  options={options}
  data={data}
  plugins={plugins}
/>;

注意:该方案每次month变化都会重建整个图表实例,性能开销大于方案1,不适合数据更新频繁、图表配置复杂的场景。

实现注意事项
  • 不要在组件渲染函数内部直接定义传给Doughnut组件的plugins数组,否则每次渲染都会生成新的插件引用,可能导致重复注册、内存泄漏问题。
  • 自定义插件内需要用到的外部动态值,优先挂载到chart实例的options配置上读取,不要直接依赖组件作用域的变量,从根源上避免闭包陈旧值问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 22:39:16