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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 10:37:25