这可能是在 React 项目中使用 ECharts 的最佳实践
但总觉得不够完善,比如不能自适应容器尺寸,或者没法开箱即用
最终我咬一咬牙,写了一个
一、快速上手
把大象放进冰箱需要几步?
打开冰箱、把大象放进去、关上冰箱,只要三步。
在 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();
}
导致在引入 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