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! 🚀

相关推荐
xuco1 小时前
如何让流式输出的 Markdown 渲染更丝滑
前端·agent
问心无愧05131 小时前
ctf show web入门91
android·前端·笔记
YAwu111 小时前
JavaScript 作用域与执行机制深度解析
前端·javascript
码界筑梦坊1 小时前
119-基于Python的各类企业排行数据可视化分析系统
开发语言·python·信息可视化·数据分析·毕业设计·echarts·fastapi
暗不需求1 小时前
深入理解 React 受控组件与非受控组件:从源码到面试
前端·react.js·面试
Yue1681 小时前
天津理工大学前端组大一末期考核随记(2)
前端·javascript
冰凌时空1 小时前
Swift 类型系统入门:从 Int、String 到自定义类型
前端·ios·ai编程
hexu_blog1 小时前
前端vue后端java+springboot如何实现pdf,word,excel之间的相互转换
java·前端·vue.js·spring boot·文档转换
w_t_y_y2 小时前
vue父子组件通信(二)祖先调用inject
前端·javascript·vue.js