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

如何强制重渲染react-chartjs-2的Chart组件并更新自定义插件?

问题:react-chartjs-2自定义Tooltip插件主题切换后需刷新才生效

我为react-chartjs-2的折线图实现了一个自定义插件,让所有数据点的Tooltip支持深色(#151429)和浅色(#FFF)两种背景色,但切换主题后,Tooltip样式必须刷新页面才会更新,无法实时生效。

原代码

import React, { useEffect, useState } from "react";
import { Chart as ChartJS} from "chart.js";
import { Line } from "react-chartjs-2";
import faker from "faker";

const labels = ["January", "February", "March", "April", "May", "June", "July"];

export function App() {
  const [theme, setTheme] = useState(() => {
    const initialTheme = localStorage.getItem("theme");
    return initialTheme ? initialTheme : "light";
  });

  function getThemeFromLocalStorage() {
    const savedTheme = localStorage.getItem("theme");
    if (savedTheme) {
      setTheme(savedTheme);
    }
  }

  function toggleTheme() {
    setTheme((prevTheme) => {
      const newTheme = prevTheme === "light" ? "dark" : "light";
      localStorage.setItem("theme", newTheme);
      return newTheme;
    });
  }

  useEffect(() => {
    getThemeFromLocalStorage();
  }, [theme]);

  const allTooltips = theme?.startsWith("d") ? "#151429" : "#FFF";

  const data = {
    labels,
    datasets: [
      {
        fill: true,
        label: "Dataset 2",
        data: labels.map(() => faker.datatype.number({ min: 0, max: 1000 })),
        borderColor: "rgb(53, 162, 235)",
        backgroundColor: "rgba(53, 162, 235, 0.5)",
      },
    ],
  };

  const alwaysShowTooltip = {
    id: "alwaysShowTooltip",
    afterDatasetsDraw(chart) {
      const { ctx } = chart;

      ctx.save();

      chart.data.datasets.forEach((dataset, i: number) => {
        chart.getDatasetMeta(i).data.forEach((datapoint, index: number) => {
          const { x, y } = datapoint.tooltipPosition();

          const itemText = chart.data.datasets[i].data[index];
          const text = itemText !== null ? itemText + "%" : "";

          ctx.beginPath();
          ctx.fillStyle = itemText !== null ? allTooltips : "transparent";
          const textWidth = ctx.measureText(text).width;
          ctx.stroke();

          ctx.shadowOffsetX = 0;
          ctx.shadowOffsetY = 5;
          ctx.shadowBlur = 5;
          ctx.shadowColor = "rgba(0, 0, 0, 0.15)";
          ctx.roundRect(
            x - (textWidth + 10) / 2,
            y - 25,
            textWidth + 10,
            16,
            4,
          );
          ctx.fill();
          ctx.shadowColor = "transparent";

          //text
          ctx.font = "Regular 10px Poppins";
          ctx.fillStyle = "gray";
          ctx.fillText(text, x - textWidth / 2, y - 14);
        });
      });
    },
  };

  return (
    <>
      <Line options={options} data={data} plugins={[alwaysShowTooltip]} />
      <button onClick={toggleTheme}>Toggle Theme: {theme}</button>
    </>
  );
}

问题根源

  1. 插件的afterDatasetsDraw方法依赖组件外部的allTooltips变量,由于闭包特性,它只会捕获组件首次渲染时的主题值,后续主题切换后不会更新。
  2. 主题切换时没有触发图表的重绘逻辑,导致插件不会重新执行绘制。

解决方法及修改后的代码

import React, { useEffect, useState, useRef } from "react";
import { Chart as ChartJS } from "chart.js";
import { Line } from "react-chartjs-2";
import faker from "faker";

const labels = ["January", "February", "March", "April", "May", "June", "July"];

export function App() {
  const [theme, setTheme] = useState(() => {
    const initialTheme = localStorage.getItem("theme");
    return initialTheme ? initialTheme : "light";
  });
  const chartRef = useRef<any>(null);

  function toggleTheme() {
    setTheme((prevTheme) => {
      const newTheme = prevTheme === "light" ? "dark" : "light";
      localStorage.setItem("theme", newTheme);
      // 主题切换后手动触发图表更新
      chartRef.current?.update();
      return newTheme;
    });
  }

  // 初始化时同步localStorage中的主题
  useEffect(() => {
    const savedTheme = localStorage.getItem("theme");
    if (savedTheme) {
      setTheme(savedTheme);
    }
  }, []);

  const alwaysShowTooltip = {
    id: "alwaysShowTooltip",
    afterDatasetsDraw(chart) {
      const { ctx } = chart;
      // 动态获取当前主题,避免闭包导致的旧值引用
      const currentTheme = localStorage.getItem("theme") || "light";
      const tooltipBg = currentTheme.startsWith("d") ? "#151429" : "#FFF";
      // 深色模式下调整文本颜色,提升可读性
      const textColor = currentTheme.startsWith("d") ? "#ccc" : "gray";

      ctx.save();

      chart.data.datasets.forEach((dataset, i: number) => {
        chart.getDatasetMeta(i).data.forEach((datapoint, index: number) => {
          const { x, y } = datapoint.tooltipPosition();

          const itemText = chart.data.datasets[i].data[index];
          const text = itemText !== null ? itemText + "%" : "";

          ctx.beginPath();
          ctx.fillStyle = itemText !== null ? tooltipBg : "transparent";
          const textWidth = ctx.measureText(text).width;
          ctx.stroke();

          ctx.shadowOffsetX = 0;
          ctx.shadowOffsetY = 5;
          ctx.shadowBlur = 5;
          ctx.shadowColor = "rgba(0, 0, 0, 0.15)";
          ctx.roundRect(
            x - (textWidth + 10) / 2,
            y - 25,
            textWidth + 10,
            16,
            4
          );
          ctx.fill();
          ctx.shadowColor = "transparent";

          // 文本样式
          ctx.font = "Regular 10px Poppins";
          ctx.fillStyle = textColor;
          ctx.fillText(text, x - textWidth / 2, y - 14);
        });
      });

      ctx.restore();
    },
  };

  const data = {
    labels,
    datasets: [
      {
        fill: true,
        label: "Dataset 2",
        data: labels.map(() => faker.datatype.number({ min: 0, max: 1000 })),
        borderColor: "rgb(53, 162, 235)",
        backgroundColor: "rgba(53, 162, 235, 0.5)",
      },
    ],
  };

  return (
    <>
      <Line 
        ref={chartRef}
        data={data} 
        plugins={[alwaysShowTooltip]} 
      />
      <button onClick={toggleTheme}>切换主题:{theme}</button>
    </>
  );
}

关键修改说明

  • 使用useRef获取图表实例,主题切换时调用update()方法强制图表重绘,触发插件重新执行绘制逻辑。
  • 在插件内部直接从localStorage读取当前主题,绕过闭包限制,确保每次绘制都使用最新的主题值。
  • 优化深色模式下的文本颜色,解决原代码中灰色文本在深色背景下可读性差的问题。
  • 移除原代码中可能导致无限循环的useEffect依赖(原useEffect依赖theme且内部调用setTheme,会触发重复渲染)。

内容的提问来源于stack exchange,提问作者Yuriy Paraska

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 00:48:10