React使用自定义Bar柱状图组件触发无限循环问题求助
问题根因
- 自定义Bar组件的useEffect未添加实例销毁逻辑,每次触发都会创建新的Chart实例,旧实例残留会引发重复更新
- 错误将useRef返回的chartContainer加入effect依赖,ref引用本身全程稳定无需加入依赖,反而可能触发不必要的effect执行
- shadow为true时每次effect执行都会重复注册全局的barWithShadow图表类型,修改Chart.js全局配置引发连锁重绘
- Chart.js默认会直接修改传入的data对象添加内部属性,可能引发意外的状态变更判定
修复方案
第一步:修改自定义Bar组件
/* eslint-disable prefer-rest-params */ import React, { useEffect, useRef, useState } from 'react'; import { Chart } from 'chart.js'; import { barChartOptions } from './config'; // 把自定义图表类型注册逻辑移到组件外,全局只执行一次 if (typeof Chart !== 'undefined') { Chart.defaults.global.datasets.barWithShadow = Chart.defaults.global.datasets.bar; Chart.defaults.barWithShadow = Chart.defaults.bar; Chart.controllers.barWithShadow = Chart.controllers.bar.extend({ draw(ease) { Chart.controllers.bar.prototype.draw.call(this, ease); const { chart: { ctx }, } = this; ctx.save(); ctx.shadowColor = 'rgba(0,0,0,0.2)'; ctx.shadowBlur = 7; ctx.shadowOffsetX = 5; ctx.shadowOffsetY = 7; Chart.controllers.bar.prototype.draw.apply(this, arguments); ctx.restore(); }, }); } const Bar = ({ data, shadow = false }) => { const chartContainer = useRef(null); const [, setChartInstance] = useState(null); const chartInstanceRef = useRef(null); useEffect(() => { // 先销毁旧实例 if (chartInstanceRef.current) { chartInstanceRef.current.destroy(); chartInstanceRef.current = null; } if (chartContainer?.current) { const context = chartContainer.current.getContext('2d'); // 传入data的深拷贝,避免Chart修改原对象 const clonedData = JSON.parse(JSON.stringify(data)); const newChartInstance = new Chart(context, { type: shadow ? 'barWithShadow' : 'bar', options: barChartOptions, data: clonedData, }); chartInstanceRef.current = newChartInstance; setChartInstance(newChartInstance); } // 清理函数 return () => { if (chartInstanceRef.current) { chartInstanceRef.current.destroy(); chartInstanceRef.current = null; } }; // 移除chartContainer依赖 }, [data, shadow]); return <canvas ref={chartContainer} />; }; export default Bar;
第二步:修改父组件MyComponent
const MyComponent = ({ someInput }) => { // 初始值和后续赋值类型统一,改为对象 const [state2, setState2] = useState({ labels: [], datasets: [] }); useEffect(() => { setState2({ labels: ["January", "February", "March", "April", "May"], datasets: [ { label: "Rainfall", backgroundColor: [ "#B21F00", "#C9DE00", "#2FDE00", "#00A6B4", "#6800B4", ], hoverBackgroundColor: [ "#501800", "#4B5000", "#175000", "#003350", "#35014F", ], data: [65, 59, 80, 81, 56], }, ], }); }, []); return ( <div className="search"> <Bar data={state2} /> </div> ); }; export default MyComponent;
内容的提问来源于stack exchange,提问作者randomG765
相关产品推荐
相关产品推荐

