在 Vue 3 项目中使用 ECharts,如何做到按需打包、类型安全、响应式适配、自动销毁?本文分享一套可直接落地的封装方案。
一、整体思路
- 按需引入:只注册项目中用到的图表、组件、渲染器,减少打包体积。
- 统一导出 :封装一个
@/utils/echarts模块,导出配置好的echarts实例和完整类型。 - 组合式函数 :编写
useEChartshook,自动处理 DOM 挂载、配置响应式更新、窗口/元素尺寸适配、生命周期清理。
二、文件 1:@/utils/echarts/index.ts ------ 按需引入并导出
typescript
// TODO: 二次封装 ECharts 实例
import * as echarts from 'echarts/core'
// 引入使用的图表
import {
BarChart,
LineChart,
PieChart
} from 'echarts/charts'
// 系列组件
import {
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
VisualMapComponent,
GeoComponent
} from 'echarts/components'
// 功能增强模块
import {
LabelLayout,
UniversalTransition
} from 'echarts/features'
// 渲染器
import { CanvasRenderer } from 'echarts/renderers'
// 注册所有必须组件
echarts.use([
// 图表
BarChart,
LineChart,
PieChart,
// 组件
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
VisualMapComponent,
GeoComponent,
// 功能
LabelLayout,
UniversalTransition,
// 渲染
CanvasRenderer
])
// 导出类型
export * from '../../types' // 这里指向你自己的全局类型文件,也可以直接导出 echarts 自带类型
// 导出手动封装后的 echarts
export { echarts }
说明 :如果你的项目没有单独的 ../../types,可以直接改为导出 ECharts 的官方类型:
typescript
export type { ECharts, EChartsOption } from 'echarts'
三、文件 2:@/utils/echarts/types.ts ------ 统一的 EChartsOption 类型(可选)
typescript
/**
* 从 echarts/core 导入 ComposeOption 和 ECharts 类型
* ComposeOption 是 ECharts 提供的类型组合工具,用于创建包含多种组件和图表类型的配置选项类型
* ECharts 是 ECharts 实例的类型
*/
import type { ComposeOption } from 'echarts/core'
/**
* 从 echarts/charts 导入图表系列类型
*/
import type {
BarSeriesOption,
LineSeriesOption,
PieSeriesOption
} from 'echarts/charts'
/**
* 从 echarts/components 导入组件配置类型
*/
import type {
TitleComponentOption,
TooltipComponentOption,
LegendComponentOption,
GridComponentOption,
VisualMapComponentOption,
GeoComponentOption
} from 'echarts/components'
/**
* 导出具体的图表类型,方便在需要时单独使用
*/
export type { ECharts } from 'echarts/core'
export type {
BarSeriesOption,
LineSeriesOption,
PieSeriesOption
} from 'echarts/charts'
/**
* 统一的 EChartsOption 类型
* 使用 ComposeOption 组合所有需要的类型,创建一个完整的配置选项类型
* 这样可以获得更好的类型提示和类型检查
*/
export type EChartsOption = ComposeOption<
| BarSeriesOption
| LineSeriesOption
| PieSeriesOption
| TitleComponentOption
| TooltipComponentOption
| LegendComponentOption
| GridComponentOption
| VisualMapComponentOption
| GeoComponentOption
>
说明 :如果你的项目没有单独的类型文件,也可以直接使用 ECharts 官方导出的 EChartsOption,但上述手动组合可以精确控制可用组件,避免类型提示中出现未注册的组件。
四、文件 3:@/hooks/useECharts.ts ------ 核心 Hook
typescript
import { ref, watch, onMounted, onUnmounted, unref, type Ref } from 'vue'
import { echarts } from '@/utils/echarts'
import type { EChartsOption, ECharts } from '@/utils/echarts'
/**
* ECharts 图表 Hook
* 自动初始化、响应式更新配置、自动销毁、窗口自适应
*
* @param elRef - 图表挂载的 DOM 引用(可以是 Ref 或原生 DOM 元素)
* @param option - 图表配置项
* @param autoResize - 是否自动跟随窗口大小变化,默认 true
* @returns { chart, updateOption }
*/
export const useECharts = (
elRef: Ref<HTMLElement | null> | HTMLElement | null | undefined,
option: EChartsOption,
autoResize = true
) => {
const chart = ref<ECharts | null>(null)
// 初始化图表
const initChart = () => {
const dom = unref(elRef)
if (!dom) return
chart.value = echarts.init(dom)
chart.value.setOption(option)
}
// 更新配置
const updateOption = (newOption: EChartsOption) => {
chart.value?.setOption(newOption)
}
// 监听配置变化
watch(
() => option,
() => updateOption(option),
{ deep: true }
)
// 监听 DOM 元素变化
let resizeObserver: ResizeObserver | null = null
const resize = () => chart.value?.resize()
watch(
() => unref(elRef),
(newEl) => {
if (newEl) {
initChart()
if (autoResize) {
resizeObserver?.disconnect()
resizeObserver = new ResizeObserver(resize)
resizeObserver.observe(newEl)
}
}
},
{ immediate: true }
)
// 组件挂载
onMounted(() => {
initChart()
if (autoResize) {
window.addEventListener('resize', resize)
const dom = unref(elRef)
if (dom) {
resizeObserver = new ResizeObserver(resize)
resizeObserver.observe(dom)
}
}
})
// 组件卸载
onUnmounted(() => {
if (autoResize) {
window.removeEventListener('resize', resize)
resizeObserver?.disconnect()
}
chart.value?.dispose()
chart.value = null
})
return {
chart: chart.value,
updateOption,
}
}
五、使用示例
5.1 基础用法
vue
<template>
<div ref="chartRef" style="width: 100%; height: 400px" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useECharts } from '@/hooks/useECharts'
import type { EChartsOption } from '@/utils/echarts'
const chartRef = ref<HTMLElement>()
const option: EChartsOption = {
title: { text: '月度销售趋势' },
tooltip: {},
xAxis: { type: 'category', data: ['1月', '2月', '3月', '4月'] },
yAxis: { type: 'value' },
series: [{ type: 'line', data: [120, 200, 150, 80] }]
}
const { chart, updateOption } = useECharts(chartRef, option, true)
// 模拟动态更新数据
setTimeout(() => {
updateOption({
series: [{ type: 'line', data: [300, 400, 250, 100] }]
})
}, 3000)
</script>
5.2 响应式 option 自动更新
如果你的 option 是响应式数据(比如从 API 获取),且希望数据变化时图表自动更新,可以直接传入一个 ref 或 computed:
vue
<script setup>
import { ref } from 'vue'
const chartData = ref([120, 200, 150])
const option = computed(() => ({
xAxis: { data: ['A', 'B', 'C'] },
series: [{ type: 'bar', data: chartData.value }]
}))
const { chart } = useECharts(chartRef, option.value)
// 现在当 chartData 变化时,option 会重新计算,并触发 watch 自动调用 updateOption
</script>
5.3 手动控制 resize
如果某些场景下不需要自动 resize(比如弹窗中的图表),可以将第三个参数设为 false,然后手动调用 updateOption 中的 resize 逻辑(需要扩展 hook)。
六、方案优缺点总结
优点
- 体积优化:按需引入,避免打包所有 ECharts 组件。
- 类型安全:完整的 TypeScript 支持。
- 自动适配 :同时监听
window.resize和ResizeObserver,覆盖大部分场景。 - 生命周期安全:组件卸载时自动销毁图表实例,避免内存泄漏。
- 响应式更新 :
option变化时自动重绘,无需手动调用。
待改进点(可根据项目需求继续完善)
- 当前
chart返回值是chart.value(非响应式),建议改成chart本身的 ref,或提供getChart方法。 - 缺少 ECharts 事件监听封装(如
on('click'))。 - 缺少 loading 状态、错误处理、主题切换等企业级进阶功能。
你可以在此基础上按需扩展,例如:
ts
// 增加事件监听
const on = (event: string, handler: any) => chart.value?.on(event, handler)
// 增加 loading
const showLoading = () => chart.value?.showLoading()
const hideLoading = () => chart.value?.hideLoading()
七、结语
这套封装已经在多个中大型 Vue 3 项目中实践,兼顾了灵活性、性能和开发体验。如果你正为 ECharts 集成发愁,不妨直接复制以上三个文件到你的项目中,再根据实际需求微调。
Happy coding! 🚀