Skip to content

封装 ECharts 组件,我踩过的坑和思考

提示本文发布于 4 个月前,其中信息可能已经时过境迁...

项目中用到 ECharts 的地方不少,大屏、管理后台、数据报表……几乎每个项目都会遇到。但每次都是复制粘贴一堆配置,改改数据就完事。时间长了,代码里散落着十几份几乎一样的 ECharts 配置,改个主题色要翻好几个文件。

后来决定封装一个通用的 ECharts 组件,这个过程踩了一些坑,也让我对"组件封装"这件事有了更深的理解。

第一步:按需引入,别一股脑全加载

ECharts 全量引入虽然方便,但包体积不小。对于中小型系统,一般只用柱状图、折线图、饼图,完全可以按需引入。

我封装了一个 echarts.ts 工具文件,统一管理引入的图表和组件:

typescript
import * as Echarts from "echarts/core";
import { BarChart, PieChart, LineChart } from "echarts/charts";
import {
  TitleComponent,
  LegendComponent,
  TooltipComponent,
  GridComponent,
  // ... 其他组件
} from "echarts/components";
import { CanvasRenderer } from "echarts/renderers";

Echarts.use([
  TitleComponent, LegendComponent, TooltipComponent,
  GridComponent, CanvasRenderer,
  BarChart, PieChart, LineChart,
]);

export const echarts = Echarts;

这样项目中所有用到 ECharts 的地方都从这个文件引入,后续需要新增图表类型时,只改这一个文件就行。

第二步:shallowRef 的坑

初始化 chart 对象时,如果用 ref 包裹,会导致 tooltip 不显示等奇怪问题。必须用 shallowRef

typescript
const chart: ShallowRef<EChartsType | null> = shallowRef(null);

原因是 ECharts 实例对象非常复杂,ref 的深度响应式会触发不必要的渲染,影响内部事件机制。

第三步:自适应窗口大小

ECharts 默认不会跟随窗口大小变化,需要手动监听 resize 事件:

typescript
function useResize(chart: EChartsType) {
  const resizeHandler = debounce(() => {
    chart.resize();
  }, 100);

  onMounted(() => window.addEventListener("resize", resizeHandler));
  onUnmounted(() => window.removeEventListener("resize", resizeHandler));
}

记得在组件销毁时移除监听,不然会内存泄漏。

第四步:巧用 dataset 减少数据处理

后端接口返回的数据结构通常和 ECharts 需要的格式不一致,每次都要遍历重组。后来发现 ECharts 的 dataset 功能可以很好地解决这个问题:

typescript
// 后端返回的数据
const source = [
  { name: "衬衫", saleNum: 17, stockNum: 5 },
  { name: "羊毛衫", saleNum: 20, stockNum: 8 },
];

// 直接用 dataset 映射
const options = {
  dataset: { source },
  series: [{
    type: "bar",
    encode: { x: "name", y: "saleNum" },
  }],
};

这样就不需要手动遍历数据了,后端返回什么,组件就能直接消费什么。

封装的意义

封装 ECharts 组件这件事,表面上是"减少重复代码",但更深层的价值是:把"怎么画图"和"画什么图"分开了。

业务开发只需要关心数据,组件负责渲染和交互。当需要统一调整图表样式、添加水印、处理异常时,改一个地方就够了。

这种"分离关注点"的思路,其实适用于任何组件的封装。