React hooks 封装 Echarts5 组件
按需引入 ECharts 图表和组件
在 Echarts5 中,引入了新的按需引入接口,因为它可以最大程度的利用打包工具 tree-shaking 的能力,并且可以有效解决命名空间冲突的问题而且防止了内部结构的暴露。
config 配置文件
由于依赖的调整,需要手动引入的地方比较多,我们可以单独放置在一个配置文件中:
// echarts.config.js
// * 需要哪些组件和配置,请在 import 时手动添加。
import * as echarts from 'echarts/core';
// 引入用到的图表
import { BarChart, PieChart } from 'echarts/charts';
// 引入提示框、数据集等组件
import { TitleComponent, TooltipComponent } from 'echarts/components';
// 引入标签自动布局、全局过渡动画等特性
import { LabelLayout } from 'echarts/features';
// 引入 Canvas 渲染器,必须
import { CanvasRenderer } from 'echarts/renderers';
// 注册必须的组件
echarts.use([
BarChart,
PieChart,
TitleComponent,
TooltipComponent,
CanvasRenderer,
LabelLayout
]);
export default echarts;
TypeScript 支持
需要使用 TS 的小伙伴,可以在 config 配置文件中添加类型。Echarts 提供了类型接口来组合出最小的 EChartsOption
类型,需要我们往其中手动添加类型即可。
// 类型相关
// 系列类型的定义后缀都为 SeriesOption
import type { BarSeriesOption, PieSeriesOption } from 'echarts/charts';
// 组件类型的定义后缀都为 ComponentOption
import type { TooltipComponentOption, TitleComponentOption } from 'echarts/components';
// 通过引入 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
import type { ComposeOption } from 'echarts/core';
// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
export type ECOption = ComposeOption<
| BarSeriesOption
| PieSeriesOption
| TitleComponentOption
| TooltipComponentOption
>;
基本组件分装
const EChart = (props, ref) => {
const { options, style = {}} = props;
const cDom = useRef(null);
const cInstance = useRef(null);
useEffect(() => {
if (cDom.current) {
// 校验 Dom 节点上是否已经挂载了 ECharts 实例,只有未挂载时才初始化
cInstance.current = echarts.getInstanceByDom(cDom.current);
if (!cInstance.current) {
cInstance.current = echarts.init(cDom.current, null, {
renderer: 'svg',
});
}
// 设置配置项
options && cInstance.current.setOption(options);
}
return () => {
// 容器被销毁之后,销毁实例,避免内存泄漏
cInstance.current.dispose();
};
}, [cDom, options]);
return <div id="echart" ref={cDom} style={{ width: '100%', height: '80%', ...style }} />;
};
export default EChart;
cDom
:dom 容器,就可以不用 document.getElementById('echart')
写法了;
cInstance
:保存 dom 容器上的 Echarts 实例;
窗口自适应
当页面窗口发生变化的时候,我们希望图表可以自动调整大小,因此还需要添加一个 resize
监听事件。
// 窗口自适应并开启过渡动画
const resize = () => {
if(cInstance.current) {
cInstance.current.resize({
animation: { duration: 300 },
});
}
};
// 监听窗口大小
useEffect(() => {
window.addEventListener('resize', debounceResize);
return () => {
window.removeEventListener('resize', debounceResize);
};
}, []);
额外监听宽高
如果你有可能会手动修改宽度和高度,还可以额外监听它们。
const EChart = (props) => {
const { style } = props;
// 监听高度变化
useLayoutEffect(() => {
resize();
}, [style.width, style.height]);
}
自适应防抖优化
如果不想频繁触发自适应事件,我们可以加个防抖优化:
import debounce from 'lodash-es/debounce';
// 自适应防抖优化
const debounceResize = debounce(resize, 500);
并使用 debounceResize
替换 resize
。
绑定鼠标点击事件
ECharts 提供了很多的events,且主要通过 on 方法添加事件处理函数。这里,我们添加鼠标事件中的点击事件,将其绑定在接口中。
const EChart = (props) => {
const { onClick } = props;
useEffect(() => {
if (cDom.current) {
// 略......
// 添加绑定鼠标点击事件
cInstance.current.on('click', event => {
if (event && onClick) onClick(event);
});
}
}, [cDom, options]);
}
展示 loading 动画
Echarts 提供了内置的加载动画效果,我们可以在加载数据前手动调用该接口显示加载动画,通过 showLoading
和 hideloading
来控制。
const EChart = (props) => {
const { loading = false } = props;
// 展示 loading 动画
useEffect(() => {
loading
? cInstance.current.showLoading()
: cInstance.current.hideLoading();
}, [loading]);
}
暴露实例方法
对父组件暴露获取 ECharts 实例的方法,让父组件可直接通过实例调用原生函数。
const EChart = (props, ref) => {
// 略......
// 获取实例
const getInstance = () => cInstance.current;
// 暴露方法
useImperativeHandle(ref, () => ({ getInstance }));
}
export default React.forwardRef(EChart);
Memorized 缓存优化
最后,作为一个全局通用组件,我们可以使用 React.memo
做一个缓存优化,让组件在 props 不变的情况下,跳过 re-render。
export default React.memo(React.forwardRef(EChart));
完整代码
- echarts.config.js
import * as echarts from 'echarts/core';
// 引入用到的图表
import { BarChart, PieChart } from 'echarts/charts';
// 引入提示框、数据集等组件
import { TitleComponent, TooltipComponent } from 'echarts/components';
// 引入标签自动布局、全局过渡动画等特性
import { LabelLayout } from 'echarts/features';
// 引入 Canvas 渲染器,必须
import { CanvasRenderer } from 'echarts/renderers';
// 类型相关
// 系列类型的定义后缀都为 SeriesOption
import type { BarSeriesOption, PieSeriesOption } from 'echarts/charts';
// 组件类型的定义后缀都为 ComponentOption
import type { TooltipComponentOption, TitleComponentOption } from 'echarts/components';
// 通过引入 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
import type { ComposeOption } from 'echarts/core';
// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
export type ECOption = ComposeOption<
| BarSeriesOption
| PieSeriesOption
| TitleComponentOption
| TooltipComponentOption
>;
// 注册必须的组件
echarts.use([
BarChart,
PieChart,
TitleComponent,
TooltipComponent,
CanvasRenderer,
LabelLayout
]);
export default echarts;
- BaseChart.jsx
import React, { useRef, useEffect, useLayoutEffect, useImperativeHandle } from 'react';
import debounce from 'lodash-es/debounce';
import echarts from './echarts.config';
const EChart = (props, ref) => {
const {
options,
loading = false, // 受控
onClick,
style = {},
} = props;
const cDom = useRef(null);
const cInstance = useRef(null);
// 获取实例
const getInstance = () => cInstance.current;
// 窗口自适应并开启过渡动画
const resize = () => {
cInstance.current &&
cInstance.current.resize({
animation: { duration: 300 },
});
};
// 自适应防抖优化
const debounceResize = debounce(resize, 500);
useEffect(() => {
if (cDom.current) {
// 校验 Dom 节点上是否已经挂载了 ECharts 实例,只有未挂载时才初始化
cInstance.current = echarts.getInstanceByDom(cDom.current);
if (!cInstance.current) {
cInstance.current = echarts.init(cDom.current, null, {
renderer: 'svg',
});
}
// 绑定鼠标点击事件
cInstance.current.on('click', event => {
if (event && onClick) onClick(event);
});
options && cInstance.current.setOption(options);
}
return () => {
cInstance.current.dispose(); // 容器被销毁之后,销毁实例,避免内存泄漏
};
}, [cDom, options]);
// 监听高度变化
useLayoutEffect(() => {
resize();
}, [style.width, style.height]);
// 监听窗口大小
useEffect(() => {
window.addEventListener('resize', debounceResize);
return () => {
window.removeEventListener('resize', debounceResize);
};
}, [options]);
// 展示 loading 动画
useEffect(() => {
loading ? cInstance.current.showLoading() : cInstance.current.hideLoading();
}, [loading]);
// 对父组件暴露获取ECharts实例的方法,可直接通过实例调用原生函数
useImperativeHandle(ref, () => ({ getInstance }));
return <div id="echart" ref={cDom} style={{ width: '100%', height: '80%', ...style }} />;
};
export default React.memo(React.forwardRef(EChart));
参考资料
转载自:https://juejin.cn/post/7235603140261937209