如何在react-chartjs-2与Chart.js 4.2.1中避免组件重渲染优化性能
react-chartjs-2嵌套组件时闪烁、过度重渲染或消失问题
父组件中嵌套使用react-chartjs-2的Bar组件时,只要其他组件触发useEffect或useState更新,图表就会出现闪烁、过度重渲染甚至完全消失的情况。已使用最新版本的react-chartjs-2和Chart.js,尝试过用useMemo、useCallback及memo对图表组件做缓存优化,但问题仍未解决。
BarChart组件代码
import { useRef, memo, useCallback, useMemo, useEffect } from 'react'; import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ChartOptions, } from 'chart.js'; import { Bar } from 'react-chartjs-2'; import ChartDataLabels from 'chartjs-plugin-datalabels'; import { nanoid } from 'nanoid'; import useChart from '../hooks/useChart'; ChartJS.register( CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, ); const BarChart = (props: any): JSX.Element => { const { width, height, data, labels, colors, showPercentage, showValue, position, stacked, scaleLabelDisplay, scaleLabelLabel, autoParserDate, maxYAxis, otherSet } = props; const chartRef = useRef<any>(null); const { datasets } = useChart(chartRef, colors, data, otherSet); const timeUnit = (autoParserDate) ? { time: { parser: 'YYYY-MM-DD', tooltipFormat: 'll', minUnit: 'day', }, distribution: 'series', } : {}; const barData = useMemo(() => ({ labels, datasets, }), [labels, datasets]); const formatter = useCallback( (value: any, ctx: any): any => { if (showValue) return value === 0 ? null : value; if (!value || !showPercentage) return null; let sum = 0; const dataArr = ctx.chart.data.datasets[0].data; dataArr.map((data: any): void => { sum += Number(data); }); const percentage = `${(Number(value) * 100 / sum).toFixed(2)}%`; return percentage; }, [showValue, showPercentage], ); const barOptions: ChartOptions<'bar'> = useMemo(() => ({ responsive: true, maintainAspectRatio: true, plugins: { legend: { position, display: position ?? false, }, tooltip: { mode: 'index', intersect: true, }, datalabels: { formatter, color: showValue ? 'black' : 'white', padding: 20, }, }, scales: { x: { stacked: !!stacked, beginAtZero: true, min: 0, ticks: { callback(value: any, index: number, values: any) { const displayLabel = this.getLabelForValue(value); return `${displayLabel}`.length > 10 ? `${`${displayLabel}`.slice(0, 10)}...` : displayLabel; }, }, ...timeUnit, title: { display: scaleLabelDisplay ?? false, text: scaleLabelLabel ?? 'Minute', }, }, y: { stacked: !!stacked, beginAtZero: true, ticks: { callback(value: any, index: number, values: any) { const displayLabel = this.getLabelForValue(value); return `${displayLabel}`.length > 10 ? `${`${displayLabel}`.slice(0, 10)}...` : displayLabel; }, }, min: 0, max: maxYAxis, }, }, }), [ labels, datasets, position, stacked, formatter, showValue, timeUnit, scaleLabelDisplay, scaleLabelLabel, maxYAxis, ]); return ( <Bar redraw datasetIdKey={nanoid()} ref={chartRef} data={barData} options={barOptions} width={width} height={height} plugins={[ChartDataLabels as any]} /> ); }; export default memo(BarChart);
自定义hook useChart.tsx代码
import { useState, useEffect } from 'react'; import { nanoid } from 'nanoid'; const useChart = (chartRef: any, colors: any, data: any, otherSet?: any): any => { const [datasets, setDatasets] = useState<any>([]); useEffect((): void => { if (chartRef.current) { const chart = chartRef.current.getContext('2d'); const ctx = chart.chart.ctx as CanvasRenderingContext2D; const facebook = ctx.createLinearGradient(0, 0, 400, 0); facebook.addColorStop(0, '#0062FE'); facebook.addColorStop(1, '#02A9F9'); const orange = ctx.createLinearGradient(0, 0, 400, 0); orange.addColorStop(0, '#FA7800'); orange.addColorStop(1, '#F9ED7D'); const purple = ctx.createLinearGradient(0, 0, 400, 0); purple.addColorStop(0, '#2800EE'); purple.addColorStop(1, '#C099FF'); const lightGreen = ctx.createLinearGradient(0, 0, 400, 0); lightGreen.addColorStop(0, '#00ED9E'); lightGreen.addColorStop(1, '#66FF7F'); const lemonGreen = ctx.createLinearGradient(0, 0, 400, 0); lemonGreen.addColorStop(0, '#2DEE2D'); lemonGreen.addColorStop(1, '#C3FF4D'); const red = ctx.createLinearGradient(0, 0, 400, 0); red.addColorStop(0, '#F50000'); red.addColorStop(1, '#FFC466'); const pink = ctx.createLinearGradient(0, 0, 400, 0); pink.addColorStop(0, '#F5003B'); pink.addColorStop(1, '#FFB2D3'); const yellow = ctx.createLinearGradient(0, 0, 400, 0); yellow.addColorStop(0, '#EEAE2D'); yellow.addColorStop(1, '#FFFF4D'); const violet = ctx.createLinearGradient(0, 0, 400, 0); violet.addColorStop(0, '#BC2DEE'); violet.addColorStop(1, '#FF4DF8'); const sky = ctx.createLinearGradient(0, 0, 400, 0); sky.addColorStop(0, '#4DA9FF'); sky.addColorStop(1, '#2DEECB'); const line = ctx.createLinearGradient(0, 0, 400, 0); line.addColorStop(0, '#73E573'); line.addColorStop(1, '#00B906'); const lazada = ctx.createLinearGradient(0, 0, 400, 0); lazada.addColorStop(0, '#F50092'); lazada.addColorStop(1, '#EE4D2D'); const shopee = ctx.createLinearGradient(0, 0, 400, 0); shopee.addColorStop(0, '#EE4D2D'); shopee.addColorStop(1, '#FF5533'); const color = colors?.length ? colors.map((i: any) => { switch (i) { case 'orange': return orange; case 'purple': return purple; case 'lightGreen': return lightGreen; case 'lemonGreen': return lemonGreen; case 'red': return red; case 'pink': return pink; case 'yellow': return yellow; case 'violet': return violet; case 'sky': return sky; case 'line': return line; case 'lazada': return lazada; case 'shopee': return shopee; case 'facebook': return facebook; default: return i; } }) : [facebook, orange, pink, purple, lightGreen, red, lemonGreen, yellow, violet, sky, line, lazada, shopee]; if (Array.isArray(data)) { return setDatasets([ { data, label: nanoid(), backgroundColor: color, borderColor: color, lineTension: 0, barThickness: 'flex', ...otherSet, }, ]); } const datasets = Object.keys(data)?.map((i: any) => { let color = facebook; switch (data[i].color) { case 'orange': color = orange; break; case 'purple': color = purple; break; case 'lightGreen': color = lightGreen; break; case 'lemonGreen': color = lemonGreen; break; case 'red': color = red; break; case 'pink': color = pink; break; case 'yellow': color = yellow; break; case 'violet': color = violet; break; case 'sky': color = sky; break; case 'line': color = line; break; case 'lazada': color = lazada; break; case 'shopee': color = shopee; break; case 'facebook': color = facebook; break; default: color = data[i].color; break; } return { label: data[i].label, data: data[i].data, backgroundColor: color, tension: 0, ...data[i].otherSet, }; }); setDatasets(datasets); } }, [data, colors, chartRef, otherSet]); return { datasets }; }; export default useChart;
内容的提问来源于stack exchange,提问作者Thee
相关产品推荐
相关产品推荐

