封装 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 组件这件事,表面上是"减少重复代码",但更深层的价值是:把"怎么画图"和"画什么图"分开了。
业务开发只需要关心数据,组件负责渲染和交互。当需要统一调整图表样式、添加水印、处理异常时,改一个地方就够了。
这种"分离关注点"的思路,其实适用于任何组件的封装。
