Vue3 + ECharts 企业级封装实践:按需引入 + useECharts Hooks

在 Vue 3 项目中使用 ECharts,如何做到按需打包、类型安全、响应式适配、自动销毁?本文分享一套可直接落地的封装方案。

一、整体思路

  • 按需引入:只注册项目中用到的图表、组件、渲染器,减少打包体积。
  • 统一导出 :封装一个 @/utils/echarts 模块,导出配置好的 echarts 实例和完整类型。
  • 组合式函数 :编写 useECharts hook,自动处理 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 获取),且希望数据变化时图表自动更新,可以直接传入一个 refcomputed

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.resizeResizeObserver,覆盖大部分场景。
  • 生命周期安全:组件卸载时自动销毁图表实例,避免内存泄漏。
  • 响应式更新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! 🚀

相关推荐
微三云、小叶5 分钟前
排队免单系统底层设计:四种分配算法拆解,无预支资金的合规营销架构方案
java·前端·软件开发·商业模式·本地生活·商业思维
copyer_xyf11 分钟前
Python 内存分析:从栈和堆理解对象引用
前端·后端·python
whatever who cares25 分钟前
大型 React 项目的文件结构
前端·react.js·前端框架
AI_零食29 分钟前
健身室器材管理系统鸿蒙PC Electron框架编写深度解析
前端·javascript·学习·华为·electron·前端框架·鸿蒙
ZC跨境爬虫1 小时前
跟着 MDN 学 JavaScript day_2:JavaScript 初体验
开发语言·前端·javascript·学习·ecmascript
假如让我当三天老蒯1 小时前
useCallback 详细解释(从原理到用法)(自学用)
前端·react.js
a1117762 小时前
粒子化系统(3D-Particles)THreeJS react
前端·html·jetson
码农君莫笑2 小时前
深入理解 CSS Grid 布局:从入门到实战
前端·css
yingyima2 小时前
Azure Functions 定时触发器配置:Cron vs. TimerTrigger,谁主沉浮?
前端