echarts封装的思考点

  1. echarts的事件处理
  2. 监听窗口变动事件,DOM尺寸发生变化时,图表能够自适应
  3. 延迟 echarts 渲染,resize-detector
  4. ResizeObserver - 元素resize监听API
  5. 离开销毁组件实例,防止内存泄漏
  6. id重复带来的 bug,用 uuid优化
  1. import echarts from 'echarts/lib/echarts'
  2. import debounce from 'lodash/debounce'
  3. import { addListener, removeListener } from 'resize-detector'

Echarts.jsx按需引入封装

Echarts.jsx

  1. import React, {useCallback, useEffect, useRef} from 'react';
  2. import {object, number, array, oneOf, bool} from 'prop-types';
  3. import debounce from 'lodash.debounce';
  4. // 引入核心模块 echarts必须要的接口
  5. import * as echarts from 'echarts/core';
  6. // 必须引入渲染器 CanvasRenderer & SVGRenderer
  7. import {CanvasRenderer, SVGRenderer} from 'echarts/renderers';
  8. // const dpr = window.devicePixelRatio; // 设备分辨率
  9. const RenderEngine = {
  10. canvas: CanvasRenderer,
  11. svg: SVGRenderer
  12. }
  13. Echarts.propTypes = {
  14. options: object.isRequired,
  15. height: number,
  16. renderType: oneOf(['canvas', 'svg']),
  17. components: array,
  18. style: object,
  19. loading: bool,
  20. };
  21. Echarts.defaultProps = {
  22. height: 240,
  23. renderType: 'canvas',
  24. components: [],
  25. style: {},
  26. loading: false,
  27. };
  28. function Echarts(props) {
  29. const {renderType, options, style, height, components, loading} = props;
  30. const chartRef = useRef(null); // DOM节点
  31. const chartInstance = useRef(null); // Echart实例
  32. const isEmpty = !Object.keys(options || {}).length; // options是否为空
  33. const handleResize = debounce(() => {
  34. const {current} = chartInstance;
  35. if (!current) return;
  36. current.resize({animation: {duration: 500}});
  37. }, 300);
  38. // 初始化图表配置项
  39. const renderChart = useCallback(() => {
  40. if(isEmpty) return
  41. const {current} = chartRef;
  42. if (!current) {
  43. return console.error('init echarts DOM error');
  44. }
  45. // 单例模式,获取 dom容器上的实例
  46. const render = echarts.getInstanceByDom(current);
  47. chartInstance.current = render ?? echarts.init(current, null, {
  48. renderer: renderType,
  49. });
  50. showLoading(chartInstance.current)
  51. chartInstance.current.setOption(options);
  52. }, [options, renderType, loading]);
  53. useEffect(init, []);
  54. // 注册必须的组件
  55. function init() {
  56. const MapRender = RenderEngine[renderType] || CanvasRenderer;
  57. // 必须在echarts.init之前使用
  58. echarts.use([MapRender, ...components]);
  59. // 监听 resize屏幕变化
  60. window.addEventListener('resize', handleResize);
  61. return () => {
  62. window.removeEventListener('resize', handleResize);
  63. };
  64. }
  65. useEffect(() => {
  66. renderChart();
  67. return () => {
  68. const {current} = chartInstance;
  69. if (!current) return;
  70. current.dispose();
  71. }
  72. }, [chartInstance, renderChart]);
  73. function showLoading(chartInstance) {
  74. if(!loading) {
  75. return chartInstance.hideLoading()
  76. }
  77. chartInstance.showLoading("default", {
  78. text: "加载中...",
  79. color: "rgb(244, 148, 148)",
  80. textColor: "rgb(112,112, 121)",
  81. maskColor: "rgba(255, 255, 255, 0.8)",
  82. zlevel: 0,
  83. showSpinner: true,
  84. });
  85. }
  86. if(!Object.keys(options).length) {
  87. return null
  88. }
  89. const attrs = {
  90. ref: chartRef,
  91. style: {
  92. height,
  93. ...style,
  94. }
  95. }
  96. return (
  97. <div {...attrs}/>
  98. );
  99. }
  100. export default Echarts;

响应式

  1. import {useCallback, useEffect, useRef} from 'react';
  2. import {
  3. object, number, string, array, oneOf, bool, oneOfType, func,
  4. } from 'prop-types';
  5. import classnames from 'classnames';
  6. import debounce from 'lodash.debounce';
  7. import { useResizeDetector } from 'react-resize-detector';
  8. // 引入核心模块 echarts必须要的接口
  9. import * as echarts from 'echarts/core';
  10. // 必须引入渲染器 CanvasRenderer & SVGRenderer
  11. import {CanvasRenderer, SVGRenderer} from 'echarts/renderers';
  12. import { UniversalTransition } from 'echarts/features';
  13. // const dpr = window.devicePixelRatio; // 设备分辨率
  14. const RenderEngine = {
  15. canvas: CanvasRenderer,
  16. svg: SVGRenderer
  17. }
  18. ECharts.propTypes = {
  19. options: object.isRequired,
  20. height: oneOfType([number, string]),
  21. renderType: oneOf(['canvas', 'svg']),
  22. components: array,
  23. style: object,
  24. loading: bool,
  25. className: string,
  26. onClick: func,
  27. };
  28. ECharts.defaultProps = {
  29. height: 240,
  30. renderType: 'canvas',
  31. components: [],
  32. style: {},
  33. loading: false,
  34. onClick: () => {},
  35. };
  36. function ECharts(props) {
  37. const {
  38. renderType, options, components, loading,
  39. className, style, height, onClick,
  40. } = props;
  41. // DOM节点
  42. const { width, ref } = useResizeDetector();
  43. const chartInstance = useRef(null); // Echart实例
  44. const handleResize = debounce(() => {
  45. const {current} = chartInstance;
  46. if (!current) return;
  47. current.resize({animation: {duration: 500}});
  48. }, 300);
  49. // 初始化图表配置项
  50. const renderChart = useCallback(() => {
  51. const isEmpty = !Object.keys(options || {}).length; // options是否为空
  52. if(isEmpty) {
  53. return null;
  54. }
  55. const {current} = ref;
  56. if (!current) {
  57. return console.error('init echarts DOM error');
  58. }
  59. // 单例模式,获取 dom容器上的实例
  60. const render = echarts.getInstanceByDom(current);
  61. chartInstance.current = render ?? echarts.init(current, null, {
  62. renderer: renderType,
  63. locale: 'ZH',
  64. });
  65. showLoading(chartInstance.current)
  66. // chartInstance.current.clear();
  67. chartInstance.current.setOption(options);
  68. chartInstance.current.off('click');
  69. chartInstance.current.on('click', onClick);
  70. }, [options, renderType, loading]);
  71. useEffect(init, []);
  72. useEffect(update, [width, height]);
  73. useEffect(() => {
  74. renderChart();
  75. return () => {
  76. const {current} = chartInstance;
  77. if (!current) return;
  78. current.dispose();
  79. }
  80. }, [chartInstance, renderChart]);
  81. // 注册必须的组件
  82. function init() {
  83. const MapRender = RenderEngine[renderType] || CanvasRenderer;
  84. // 必须在echarts.init之前使用
  85. echarts.use([
  86. MapRender,
  87. UniversalTransition,
  88. ...components
  89. ]);
  90. // 监听 resize屏幕变化
  91. window.addEventListener('resize', handleResize);
  92. return () => {
  93. window.removeEventListener('resize', handleResize);
  94. };
  95. }
  96. function update() {
  97. if(!width) return;
  98. handleResize();
  99. }
  100. function showLoading(chartInstance) {
  101. if(!loading) {
  102. return chartInstance.hideLoading()
  103. }
  104. chartInstance.showLoading("default", {
  105. text: "加载中...",
  106. color: "rgb(244, 148, 148)",
  107. textColor: "rgb(112,112, 121)",
  108. maskColor: "rgba(255, 255, 255, 0.8)",
  109. zlevel: 0,
  110. showSpinner: true,
  111. });
  112. }
  113. if(!Object.keys(options).length) {
  114. return null
  115. }
  116. const attrs = {
  117. className: classnames(className),
  118. ref,
  119. style: {
  120. height,
  121. ...style,
  122. }
  123. }
  124. return (
  125. <div {...attrs}/>
  126. );
  127. }
  128. export default ECharts;

echarts目录规范

image.png