echarts封装的思考点
- echarts的事件处理
- 监听窗口变动事件,DOM尺寸发生变化时,图表能够自适应
- 延迟 echarts 渲染,resize-detector
- ResizeObserver - 元素resize监听API
- 离开销毁组件实例,防止内存泄漏
- id重复带来的 bug,用 uuid优化
import echarts from 'echarts/lib/echarts'
import debounce from 'lodash/debounce'
import { addListener, removeListener } from 'resize-detector'
Echarts.jsx按需引入封装
Echarts.jsx
import React, {useCallback, useEffect, useRef} from 'react';
import {object, number, array, oneOf, bool} from 'prop-types';
import debounce from 'lodash.debounce';
// 引入核心模块 echarts必须要的接口
import * as echarts from 'echarts/core';
// 必须引入渲染器 CanvasRenderer & SVGRenderer
import {CanvasRenderer, SVGRenderer} from 'echarts/renderers';
// const dpr = window.devicePixelRatio; // 设备分辨率
const RenderEngine = {
canvas: CanvasRenderer,
svg: SVGRenderer
}
Echarts.propTypes = {
options: object.isRequired,
height: number,
renderType: oneOf(['canvas', 'svg']),
components: array,
style: object,
loading: bool,
};
Echarts.defaultProps = {
height: 240,
renderType: 'canvas',
components: [],
style: {},
loading: false,
};
function Echarts(props) {
const {renderType, options, style, height, components, loading} = props;
const chartRef = useRef(null); // DOM节点
const chartInstance = useRef(null); // Echart实例
const isEmpty = !Object.keys(options || {}).length; // options是否为空
const handleResize = debounce(() => {
const {current} = chartInstance;
if (!current) return;
current.resize({animation: {duration: 500}});
}, 300);
// 初始化图表配置项
const renderChart = useCallback(() => {
if(isEmpty) return
const {current} = chartRef;
if (!current) {
return console.error('init echarts DOM error');
}
// 单例模式,获取 dom容器上的实例
const render = echarts.getInstanceByDom(current);
chartInstance.current = render ?? echarts.init(current, null, {
renderer: renderType,
});
showLoading(chartInstance.current)
chartInstance.current.setOption(options);
}, [options, renderType, loading]);
useEffect(init, []);
// 注册必须的组件
function init() {
const MapRender = RenderEngine[renderType] || CanvasRenderer;
// 必须在echarts.init之前使用
echarts.use([MapRender, ...components]);
// 监听 resize屏幕变化
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}
useEffect(() => {
renderChart();
return () => {
const {current} = chartInstance;
if (!current) return;
current.dispose();
}
}, [chartInstance, renderChart]);
function showLoading(chartInstance) {
if(!loading) {
return chartInstance.hideLoading()
}
chartInstance.showLoading("default", {
text: "加载中...",
color: "rgb(244, 148, 148)",
textColor: "rgb(112,112, 121)",
maskColor: "rgba(255, 255, 255, 0.8)",
zlevel: 0,
showSpinner: true,
});
}
if(!Object.keys(options).length) {
return null
}
const attrs = {
ref: chartRef,
style: {
height,
...style,
}
}
return (
<div {...attrs}/>
);
}
export default Echarts;
响应式
import {useCallback, useEffect, useRef} from 'react';
import {
object, number, string, array, oneOf, bool, oneOfType, func,
} from 'prop-types';
import classnames from 'classnames';
import debounce from 'lodash.debounce';
import { useResizeDetector } from 'react-resize-detector';
// 引入核心模块 echarts必须要的接口
import * as echarts from 'echarts/core';
// 必须引入渲染器 CanvasRenderer & SVGRenderer
import {CanvasRenderer, SVGRenderer} from 'echarts/renderers';
import { UniversalTransition } from 'echarts/features';
// const dpr = window.devicePixelRatio; // 设备分辨率
const RenderEngine = {
canvas: CanvasRenderer,
svg: SVGRenderer
}
ECharts.propTypes = {
options: object.isRequired,
height: oneOfType([number, string]),
renderType: oneOf(['canvas', 'svg']),
components: array,
style: object,
loading: bool,
className: string,
onClick: func,
};
ECharts.defaultProps = {
height: 240,
renderType: 'canvas',
components: [],
style: {},
loading: false,
onClick: () => {},
};
function ECharts(props) {
const {
renderType, options, components, loading,
className, style, height, onClick,
} = props;
// DOM节点
const { width, ref } = useResizeDetector();
const chartInstance = useRef(null); // Echart实例
const handleResize = debounce(() => {
const {current} = chartInstance;
if (!current) return;
current.resize({animation: {duration: 500}});
}, 300);
// 初始化图表配置项
const renderChart = useCallback(() => {
const isEmpty = !Object.keys(options || {}).length; // options是否为空
if(isEmpty) {
return null;
}
const {current} = ref;
if (!current) {
return console.error('init echarts DOM error');
}
// 单例模式,获取 dom容器上的实例
const render = echarts.getInstanceByDom(current);
chartInstance.current = render ?? echarts.init(current, null, {
renderer: renderType,
locale: 'ZH',
});
showLoading(chartInstance.current)
// chartInstance.current.clear();
chartInstance.current.setOption(options);
chartInstance.current.off('click');
chartInstance.current.on('click', onClick);
}, [options, renderType, loading]);
useEffect(init, []);
useEffect(update, [width, height]);
useEffect(() => {
renderChart();
return () => {
const {current} = chartInstance;
if (!current) return;
current.dispose();
}
}, [chartInstance, renderChart]);
// 注册必须的组件
function init() {
const MapRender = RenderEngine[renderType] || CanvasRenderer;
// 必须在echarts.init之前使用
echarts.use([
MapRender,
UniversalTransition,
...components
]);
// 监听 resize屏幕变化
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}
function update() {
if(!width) return;
handleResize();
}
function showLoading(chartInstance) {
if(!loading) {
return chartInstance.hideLoading()
}
chartInstance.showLoading("default", {
text: "加载中...",
color: "rgb(244, 148, 148)",
textColor: "rgb(112,112, 121)",
maskColor: "rgba(255, 255, 255, 0.8)",
zlevel: 0,
showSpinner: true,
});
}
if(!Object.keys(options).length) {
return null
}
const attrs = {
className: classnames(className),
ref,
style: {
height,
...style,
}
}
return (
<div {...attrs}/>
);
}
export default ECharts;