如何在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
相关产品推荐
相关产品推荐

