Closed xsjcTony closed 5 months ago
Hi @xsjcTony, I'm currently exposing the parameters from the ECharts "use" function. Any ideas about how could we improve it?
Actually, (I didn't do any experiments yet) I might be able to define the type myself https://echarts.apache.org/handbook/en/basics/import#creating-an-option-type-in-typescript and do such following:
Since the composed ECOptions
should always be the subtype of EChartsOptions
(not 100% sure, but ideally since it's provided by echarts itself), so options
are always passable to <ECharts />
.
The only thing is that how we can narrow the prop type of CustomizedECharts
, that we might need to introduce a generic type parameter for both useECharts
and ECharts
component, that would replace EChartsOptions
, but I'm not too sure.
By doing that, we may need to cast the type somewhere in the implementations, otherwise the destructuring against generic type will gonna be a nightmare. It's generally OK to cast so, since passing down undefined
to init
or setOptions
is not harmful at all if some of options are missing.
If the below is working, it might worth to include in the documentation, at least we have a way to narrow the type ourselves without actually making any changes to this lib. Although it's kind of common sense to TS developers, but yeah, why not mention itπ
export type ECOption = ComposeOption<
| BarSeriesOption
| LineSeriesOption
| TitleComponentOption
| TooltipComponentOption
| GridComponentOption
| DatasetComponentOption
>
const CustomizedECharts: FC<Omit<EChartProps, 'use'>> = (props) => {
return <ECharts {...props} use={/* Modules to be used */} />
}
export default CustomizedECharts
const options: ECOption = {
// ...
}
return <CustomizedECharts{...options} />
I love the idea, great suggestion. I've been trying to come up with a proposal but got some issues and not much time to work on this π, would you be up to creating a PR?
Same here, but will take a look if time allows.
Below is what I implemented myself (of course far from what you have done, but satisfies my needs), but it does not have the issues described in https://github.com/hugocxl/react-echarts/issues/19 and https://github.com/hugocxl/react-echarts/issues/20.
And maybe you can take the idea of another generic type to constraint option type to used modules only.
eCharts.ts
:
import { useUpdateEffect } from 'ahooks';
import { init, use } from 'echarts/core';
import { useEffect, useRef } from 'react';
import { nextTick } from '@utils/dom';
import type { EChartsOption } from 'echarts';
import type { ECharts, EChartsCoreOption } from 'echarts/core';
import type { RefCallback } from 'react';
type UseEChartsOptions<Options extends EChartsCoreOption> = {
options: Options;
modules?: Parameters<typeof use>[0];
theme?: Parameters<typeof init>[1];
initOptions?: Parameters<typeof init>[2];
};
const useECharts = <
T extends HTMLElement,
Options extends EChartsCoreOption = EChartsOption,
>(params: UseEChartsOptions<Options>): RefCallback<T> => {
const {
modules,
theme,
initOptions,
options,
} = params;
const containerRef = useRef<T | null>(null);
const eChartsRef = useRef<ECharts | null>(null);
const initChart = async (): Promise<void> => {
if (!containerRef.current || eChartsRef.current)
return;
modules && use(modules);
const eChartsInstance = init(containerRef.current, theme, initOptions);
await nextTick();
eChartsInstance.setOption(options);
eChartsRef.current = eChartsInstance;
};
const disposeChart = (): void => {
eChartsRef.current?.dispose();
eChartsRef.current = null;
};
const containerRefCallback: RefCallback<T> = async (dom) => {
if (containerRef.current && eChartsRef.current)
return;
containerRef.current = dom;
await initChart();
};
useUpdateEffect(() => eChartsRef.current?.setOption(options), [options]);
useEffect(() => {
const resizeObserver = new ResizeObserver(() => eChartsRef.current?.resize());
containerRef.current && resizeObserver.observe(containerRef.current);
return () => {
resizeObserver.disconnect();
disposeChart();
eChartsRef.current?.clear();
};
}, []);
useUpdateEffect(() => {
disposeChart();
void initChart();
}, [theme, initOptions]);
return containerRefCallback;
};
export default useECharts;
ECharts.tsx
:
/* eslint-disable ts/sort-type-constituents */
import { theme } from 'antd';
import { BarChart } from 'echarts/charts';
import { GridComponent, TooltipComponent, TitleComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import useECharts from '@components/ECharts/hooks/eCharts';
import type { BarSeriesOption } from 'echarts/charts';
import type { GridComponentOption, TooltipComponentOption, TitleComponentOption } from 'echarts/components';
import type { ComposeOption, init } from 'echarts/core';
import type { FC } from 'react';
type EChartsInitOptions = NonNullable<Parameters<typeof init>[2]>;
export type EChartsOptions = ComposeOption<
| BarSeriesOption
| GridComponentOption
| TooltipComponentOption
| TitleComponentOption
>;
type EChartsProps = {
options: EChartsOptions;
width?: EChartsInitOptions['width'];
height: NonNullable<EChartsInitOptions['height']>;
};
const ECharts: FC<EChartsProps> = ({
options,
width,
height,
}) => {
const { token: { colorPrimary } } = theme.useToken();
const refCallback = useECharts<HTMLDivElement, EChartsOptions>({
initOptions: { width, height },
modules: [
BarChart,
GridComponent,
TooltipComponent,
TitleComponent,
CanvasRenderer,
],
options: {
color: [colorPrimary],
...options,
},
});
return <div ref={refCallback} />;
};
export default ECharts;
Description
The
use
prop is a really good wrapper to reduce bundle size: https://hugocxl.github.io/react-echarts/docs/bundle-size However, wondering if we have a way (e.g. generic) that can narrow down the prop type to those modules we included only? https://echarts.apache.org/handbook/en/basics/import#creating-an-option-type-in-typescript just like in the official docsProblem Statement/Justification
For better type-checking in case of using in-exist props
Proposed Solution or API
Maybe generic?
Alternatives
No response
Additional Information
No response