首页 > 解决方案 > 如何从反应组件道具中进行 ChartJS 配置的类型传输?

问题描述

我制作了一个ChartJS组件,它是一个简单的反应包装器,可以使用一些默认选项正确设置和销毁 ChartJS 实例:

import React, {
  useEffect,
  useState,
  VFC,
} from 'react';
import {
  ArcElement,
  BarController,
  BarElement,
  CategoryScale,
  Chart,
  ChartConfiguration,
  DoughnutController,
  LinearScale,
} from 'chart.js';

Chart.register(
  ArcElement,
  BarElement,
  BarController,
  DoughnutController,
  CategoryScale,
  LinearScale,
);

export interface ChartJSProps {
  config: ChartConfiguration;
}

export const ChartJS: VFC<ChartJSProps> = ({
  config,
}) => {
  const [id, setId] = useState<string>();

  useEffect(() => {
    setId(`chart-${Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1)}`);
  }, []);

  useEffect(() => {
    if (!id) {
      return;
    }

    // @see https://stackoverflow.com/a/58877962/1731473
    const canvas: HTMLCanvasElement = document.getElementById(id) as HTMLCanvasElement;
    const ctx = canvas.getContext('2d');
    let chart: Chart;
    if (ctx !== null) {
      const { options } = config;
      chart = new Chart(ctx, {
        ...config,
        options: {
          responsive: true,
          maintainAspectRatio: false,
          ...options,
          elements: {
            ...options?.elements,
            line: {
              fill: false,
              ...options?.elements?.line,
            },
          },
          plugins: {
            ...options?.plugins,
            legend: {
              display: false,
              ...options?.plugins?.legend,
            },
            tooltip: {
              mode: 'nearest',
              intersect: false,
              ...options?.plugins?.tooltip,
            },
          },
        },
      });
    }

    return () => {
      if (chart) {
        // @see https://www.chartjs.org/docs/latest/developers/api.html#destroy
        chart.destroy();
      }
    };
  }, [id]);

  return (
    <canvas id={id} />
  );
};

export default null;

这个包装器可以正常工作,但是,Typescript 对特定类型的配置大喊大叫。

下面以一个GaugeChart使用 ChartJSdoughnut类型的组件为例:

import React, {
  VFC,
} from 'react';
import { ChartJS } from './ChartJS';

export interface GaugeChartProps {
  value: number;
  max?: number;
}

export const GaugeChart: VFC<GaugeChartProps> = ({
  value,
  max = 100,
}) => {
  const percent = value / max;

  return (
    <ChartJS config={{
      type: 'doughnut',
      data: {
        datasets: [
          {
            backgroundColor: [
              'rgb(255, 99, 132)',
              '#ccc',
            ],
            // eslint-disable-next-line id-blacklist
            data: [
              percent * 100,
              100 - (percent * 100),
            ],
          },
        ],
      },
      options: {
        rotation: 270, // start angle in degrees
        circumference: 180, // sweep angle in degrees
      },
    }}
    />
  );
};

export default null;

npx tsc命令给了我这个:

src/components/chart/GaugeChart.tsx:36:9 - error TS2322: Type '{ rotation: number; circumference: number; }' is not assignable to type '_DeepPartialObject<CoreChartOptions<keyof ChartTypeRegistry> & ElementChartOptions<keyof ChartTypeRegistry> & PluginChartOptions<...> & DatasetChartOptions<...> & ScaleChartOptions<...>>'.
  Object literal may only specify known properties, and 'rotation' does not exist in type '_DeepPartialObject<CoreChartOptions<keyof ChartTypeRegistry> & ElementChartOptions<keyof ChartTypeRegistry> & PluginChartOptions<...> & DatasetChartOptions<...> & ScaleChartOptions<...>>'.

36         rotation: 270, // start angle in degrees
           ~~~~~~~~~~~~~

  node_modules/chart.js/types/index.esm.d.ts:3493:3
    3493   options?: ChartOptions<TType>;
           ~~~~~~~
    The expected type comes from property 'options' which is declared here on type 'ChartConfiguration<keyof ChartTypeRegistry, (number | ScatterDataPoint | BubbleDataPoint | null)[], unknown>'

打字稿版本:

❯ npx tsc -v
Version 4.2.2

如您所见,该rotation选项无法识别。

当然,我可以通过声明一个类型良好的常量来解决它:

import React, {
  VFC,
} from 'react';
import { ChartConfiguration } from 'chart.js';
import { ChartJS } from './ChartJS';

export const GaugeChart: VFC<GaugeChartProps> = ({
  value,
  max = 100,
}) => {
  const percent = value / max;
  const config: ChartConfiguration<'doughnut'> = {
    type: 'doughnut',
    data: {
      datasets: [
        {
          backgroundColor: [
            'rgb(255, 99, 132)',
            '#ccc',
          ],
          // eslint-disable-next-line id-blacklist
          data: [
            percent * 100,
            100 - (percent * 100),
          ],
        },
      ],
    },
    options: {
      rotation: 270, // start angle in degrees
      circumference: 180, // sweep angle in degrees
    },
  };

  return <ChartJS config={config} />;
};

但这意味着我必须直接依赖chart.js供应商,这看起来并不理想。

此外,type配置值应该推断定义,因为它是通过直接配置options的简单调用完成的。new Chart

如何type在我的组件包装器config道具上具有与值推断相同的行为?

标签: reactjstypescriptchart.js

解决方案


推荐阅读