Talk is cheap. Show me your code

这可能是在 React 项目中使用 ECharts 的最佳实践

项目经常会用到 echarts, 也尝试过 echarts-for-react、原生封装等实践方案

但总觉得不够完善,比如不能自适应容器尺寸,或者没法开箱即用

最终我咬一咬牙,写了一个 react-echarts-core

 

一、快速上手

把大象放进冰箱需要几步?

打开冰箱、把大象放进去、关上冰箱,只要三步。

在 React 项目中使用 ECharts 图表需要几步?

同样只要三步:

Step 1: 安装 react-echarts-core

npm install react-echarts-core echarts  --save
#or
yarn add react-echarts-core echarts

Step 2: 创建图表配置 option

import ChartCore from 'react-echarts-core';
import type { EChartsOption } from 'react-echarts-core';
​
const Demo = () => {
  const option: EChartsOption = {
    // ...
  };
}

Step 3: 使用 react-echarts-core 渲染 echarts 图表

<ChartCore option={option} />

 

 

二、更多场景

1. 使用其他类型的图表

react-echarts-core 默认支持饼图(PieChart)、折线图(LineChart)、柱状图(BarChart)

如果需要使用其他类型的图表,需要使用内置的 use 函数引入

import { ScatterChart } from 'echarts/charts';
import ChartCore, { use } from 'react-echarts-core';

use([ScatterChart]);

const Demo = () => {
  // ...
}

除了上面三个基础图表类型以外,react-echarts-core 还内置了以下组件:

import { TooltipComponent, GridComponent, LegendComponent } from 'echarts/components';

同样的,如需使用其他组件(如 DataZoomComponent ),也需要使用 use 函数注册

 

2. 获取 echarts 实例

react-echarts-core 提供了 onChartReady 回调函数,可以获取 echarts 实例

可以通过 echarts 实例实现“事件处理”等操作

import React, { useCallback, useRef } from 'react';
import ChartCore from 'react-echarts-core';
import type { EChartsOption, EChartsType } from 'react-echarts-core';

const Demo: React.FC = () => {
  const chartInstance = useRef<EChartsType>();

  const option: EChartsOption = {
    // ...
  };

  // 绑定事件
  const bind = useCallback((ref: EChartsType) => {
    if (!ref) return;
    ref.on('click', params => {
      // do sth...
    });
  }, []);

  // 通过加载图表成功的回调获取 echarts 实例
  const onChartReady = useCallback((ref: EChartsType) => {
    chartInstance.current = ref;
    bind(ref);
  }, [bind]);

  return (
    <ChartCore option={option} onChartReady={onChartReady} />
  );
};

 

3. 更新时清除画布

如果给 react-echarts-core 传入 clear={true}, 则图表数据更新时,会清除画布

<ChartCore option={option} clear />

这个属性在大部分情况下都用不上,但如果遇到 option 更新了但画布没有更新的情况,而且尝试过其他方案后依旧无法解决,可以考虑使用 clear 来处理

 


以上便是 react-echarts-core 的用法

目前大部分 react 项目中使用 echarts 的方案,都没有解决我的两个需求痛点:

1. 图表不能自适应容器

2. 不能开箱即用,需要额外引入 `echarts/core`  及其他 echarts 模块

接下来会聊一聊我是怎么解决这两个问题的

 

三、开发历程 - 自适应容器尺寸

echarts 本身提供了一个 resize 方法调整画布尺寸,但最大的问题在于:什么时候调用 echarts.resize

在项目实践中,最常见的是 window.resize 导致容器尺寸变化,所以常规方式是监听 window.resize 事件,调用 echarts.resize

但除此之外,也有可能窗口大小没变,页面内部的尺寸发生变化,比如最常见的“侧边栏展开/收起”

对于这种场景,监听 window.resize 的方案就不再适用,如果能直接监听容器的 resize 事件就完美了

好在 Web 技术发展迅速,还真有这么一个监听 DOM 尺寸变化的方法:Resize Observer API

不过作为一个新技术,兼容性会稍差一点,这时候可以使用兼容方案: resize-observer-polyfill 或者 @juggle/resize-observer

最终我使用的是 @juggle/resize-observer, 因为包体积更小...

 

四、开发历程 - 注册更多 echarts 模块

为达到开箱即用的效果,就需要在 react-echarts-core 内部注册一些 echarts 基础模块,同时为又不能影响开发者的外部扩展

注册 echarts 模块需要使用 echarts.use 方法,这个方法已经解决了模块重复注册的问题

但对于 'echarts/renderers' 却存在问题

echarts 的图表在渲染之前,需要引入 CanvasRenderer 或者 SVGRenderer 以明确图表渲染的方案

也就是在第一次调用 echarts.use 的时候就需要指定 render,而后面传入其他 render 是无效的

这么一来,echarts 的 render 就锁死了,无法在使用 react-echarts-core 时指定 render

 

最终我基于 echarts.use 封装了一个新的 use 函数并暴露出来

这个函数以异步函数的形式统计一次事件循环中传入的所有 echarts 模块,这样就能按实际配置选择 render

const componentsList: ChartsComponents = [];
const renderList: EchartsRender[] = [];

function useEcharts(components: ChartsComponents, renders?: EchartsRender[]) {
  const baseComponents: ChartsComponents = [
    // ...
  ];
  const currentRender = renders?.[0] || CanvasRenderer;
  echarts.use([...baseComponents, ...components, currentRender]);
}

const debounceUseEcharts = debounce(useEcharts, 0);

export default function use(components?: ChartsComponents, render?: EchartsRender) {
  const main = () => {
    componentsList.push(...(Array.isArray(components) ? components : []));
    render && renderList.push(render);
    debounceUseEcharts(componentsList, renderList);
  };

  return main();
}

 

五、开发历程 - CSS in JS

上面在处理 resize 的时候,在内部给 echarts DOM 节点加一个包装容器,并写了几行基础样式

导致在引入 react-echarts-core 时,还需要引入一个 react-echarts-core.css 文件

这样就违背了“减负”的初衷,有没有什么办法能在引入 react-echarts-core 的同时引入样式呢?

一个简单的 CSS in JS 的方案在脑海中浮现~

export interface StyleItem {
  className: string,
  styles: CSSProperties,
}

function renderStyleItem(style: StyleItem): string {
  const className = style?.className || '';
  const id = `${STYLE_PREFIX}-${className}`;

  // 如果已创建过 style 则跳过
  if (window.document && !window.document.getElementById(id)) {
    const styleTag = document.createElement('style');
    styleTag.id = id;
    // style 对象转字符串
    styleTag.innerText = getStyleText(className, style.styles);
    document.head.append(styleTag);
  }

  return className;
}

export default function renderStyle(styles: StyleItem | StyleItem[]) {
  const v: StyleItem[] = Array.isArray(styles) ? styles : [styles];
  return v.map(renderStyleItem).join(' ');
}

将原本的 css 文件改为 StyleItem 对象的写法,这样就能在加载组件的时候,动态插入 <style> 标签并写入样式

这样就解决了额外引入 css 文件的问题,真正实现开箱即用~

 


 

最后奉上 GitHub 仓库地址: https://github.com/wisewrong/react-echarts-core,欢迎 star or issue

 

posted @ 2022-12-13 11:23  Wise.Wrong  阅读(3639)  评论(0编辑  收藏  举报