Vue3 组件封装与按需引入
🎯 引言
前面每画一个图都要写一遍 init、resize、卸载清理,重复又容易漏。学会这篇文章,你能把 ECharts 封装成一个通用的 Vue3 组件,一行代码画出任意图表;还会用按需引入把打包体积降下来,完成从“会用”到“用好”的最后一跃。
🧱 为什么要封装
直接在业务组件里用 ECharts 有三个痛点:
- 重复代码:init、setOption、resize 监听,每个图表都要写一遍。
- 容易内存泄漏:组件销毁时忘记
dispose(),图表实例一直占着内存。 - 写法不统一:团队里每个人写一套,维护成本高。
封装的目标:业务代码只关心 option,其余全部收进组件。
🛠 封装 BaseChart 组件
<template>
<div ref="chartRef" class="chart"></div>
</template>
<script setup>
import { onBeforeUnmount, onMounted, ref, watch } from 'vue';
import * as echarts from 'echarts';
const props = defineProps({
option: {
type: Object,
required: true,
},
});
const chartRef = ref(null);
let chart = null;
const handleResize = () => {
chart && chart.resize();
};
onMounted(() => {
chart = echarts.init(chartRef.value);
chart.setOption(props.option);
window.addEventListener('resize', handleResize);
});
// option 变化时自动更新图表
watch(
() => props.option,
(newOption) => {
chart && chart.setOption(newOption);
},
{ deep: true }
);
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize);
chart && chart.dispose();
chart = null;
});
</script>
<style scoped>
.chart {
width: 100%;
height: 400px;
}
</style>
组件做了四件事:挂载时初始化、option 变化时更新、窗口变化时自适应、卸载时销毁。
使用时就一行:
<template>
<BaseChart :option="option" />
</template>
<script setup>
import { computed } from 'vue';
import BaseChart from '@/components/BaseChart.vue';
const option = computed(() => ({
title: { text: '一周销量' },
tooltip: { trigger: 'axis' },
xAxis: {
type: 'category',
data: ['周一', '周二', '周三', '周四', '周五'],
},
yAxis: { type: 'value' },
series: [
{
name: '销量',
type: 'bar',
data: [120, 200, 150, 80, 70],
},
],
}));
</script>
dispose() 一定不能漏。尤其是配合 v-if 频繁显隐图表时,不销毁的实例会越积越多,页面越来越卡。🧰 按需引入减小体积
import * as echarts from 'echarts' 会把所有图表类型和组件都打包进来,体积不小。如果项目只用几种图,可以按需引入:
import * as echarts from 'echarts/core';
// 只引入用到的图表
import { BarChart, LineChart, PieChart } from 'echarts/charts';
// 只引入用到的组件
import {
GridComponent,
LegendComponent,
TitleComponent,
TooltipComponent,
} from 'echarts/components';
// 渲染器
import { CanvasRenderer } from 'echarts/renderers';
echarts.use([
BarChart,
LineChart,
PieChart,
GridComponent,
LegendComponent,
TitleComponent,
TooltipComponent,
CanvasRenderer,
]);
export default echarts;
然后把 BaseChart.vue 里的引入换成这个模块:
import echarts from '@/utils/echarts.js';
echarts.use() 里注册对应模块。💡 Vue 中使用 ECharts 的常见坑
| 坑 | 原因 | 对策 |
|---|---|---|
| 图表不显示 | 容器高度为 0,或在 onMounted 前 init | 设好高度,在 onMounted 里初始化 |
| 页面变卡 | 组件卸载时未 dispose | 封装组件统一处理销毁 |
| 窗口缩放错位 | 没监听 resize | 封装组件统一处理自适应 |
| 数据变了图没变 | option 引用没变,watch 没触发 | 加 deep: true 或返回新对象 |
| 打包体积大 | 全量引入 echarts | 按需引入 |
🧾 小节总结
- 封装组件把 init、更新、resize、dispose 统一收拢,业务侧只传
option。 watch监听option变化自动setOption,deep: true保证深层变化也能捕获。onBeforeUnmount中移除监听并dispose(),杜绝内存泄漏。- 按需引入用
echarts/core配合echarts.use(),只打包用到的图表和组件。 - 常见坑:容器高 0、忘记销毁、忘记 resize、watch 不触发、全量引入。
❓ 知识问答
Q1:为什么 option 建议用 computed 包一层?
这样响应式数据变化时会生成新的 option,配合组件里的 watch,图表自动刷新,业务代码不需要手动调 setOption。
Q2:社区有现成的封装吗?
有,比如 vue-echarts,思路和我们封装的完全一致。自己动手封装一遍能彻底搞懂生命周期,之后用别人的库也知道它在干什么。
Q3:按需引入后还能用 echarts.graphic.LinearGradient 吗?
可以,graphic 是 echarts/core 的一部分,不受按需引入影响。
Q4:一个页面放多少个图表合适?
没有硬性上限,但每个实例都会占内存。十几个以内通常没问题,大屏场景几十个也常见,关键是确保销毁逻辑可靠。
🧪 小练习
基于本课的 BaseChart 组件完成一个“双图表页面”:
- 页面上放两个
BaseChart,一个显示柱状图、一个显示饼图。 - 准备一个按钮,点击后修改柱状图的数据,观察图表自动更新。
- 把
utils/echarts.js改成按需引入,只注册这两种图需要的模块。
<template>
<!-- 请在这里编写代码 -->
</template>
<script setup>
// 请在这里编写代码
</script>
🎉 恭喜你已经完整掌握 ECharts 的核心技能啦!从第一个图表到封装复用,你已经具备在真实项目中搞定数据可视化的能力,快去把枯燥的数据变成会说话的图表吧!
