基于 Vue3 + TypeScript 的 ECharts Hooks

612 阅读2分钟

先上代码:

// hooks/useECharts.ts
import { ref, watch, onMounted, onUnmounted, type Ref } from 'vue';
import type { ECharts, EChartsOption, Theme } from 'echarts';

export interface UseEChartsOptions {
  /** ECharts 实例 */
  echarts: any; // 根据实际安装的 echarts 类型调整
  /** 图表配置项 */
  options: EChartsOption;
  /** 主题配置 */
  theme?: Theme | string;
  /** 是否自动响应 resize 事件 */
  autoResize?: boolean;
}

export function useECharts(
  options: UseEChartsOptions
): {
  chartRef: Ref<HTMLElement | null>;
  chartInstance: Ref<ECharts | null>;
  setOptions: (opts: EChartsOption) => void;
} {
  const chartRef = ref<HTMLElement | null>(null);
  const chartInstance = ref<ECharts | null>(null);
  const { echarts, theme, autoResize = true } = options;

  // 初始化图表
  const initChart = () => {
    if (!chartRef.value) return;
    
    try {
      chartInstance.value = echarts.init(chartRef.value, theme);
      chartInstance.value.setOption(options.options);
    } catch (err) {
      console.error('ECharts initialization error:', err);
    }
  };

  // 更新配置
  const setOptions = (opts: EChartsOption) => {
    if (!chartInstance.value) return;
    chartInstance.value.setOption(opts);
  };

  // 窗口 resize 处理
  const handleResize = () => {
    if (!chartInstance.value) return;
    chartInstance.value.resize();
  };

  // 自动 resize
  const autoResizeHandler = () => {
    if (autoResize) {
      window.addEventListener('resize', handleResize);
    }
  };

  // 销毁图表
  const dispose = () => {
    if (chartInstance.value) {
      chartInstance.value.dispose();
      chartInstance.value = null;
    }
    window.removeEventListener('resize', handleResize);
  };

  // 生命周期
  onMounted(() => {
    initChart();
    autoResizeHandler();
  });

  onUnmounted(() => {
    dispose();
  });

  // 监听 options 变化
  watch(
    () => options.options,
    (newOptions) => {
      setOptions(newOptions);
    },
    { deep: true }
  );

  return {
    chartRef,
    chartInstance,
    setOptions
  };
}

使用示例:

<template>
  <div ref="chartRef" style="width: 100%; height: 400px;"></div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { useECharts } from '@/hooks/useECharts';
import * as echarts from 'echarts'; // 按需引入

const options = ref({
  title: {
    text: '示例图表'
  },
  xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
  },
  yAxis: {
    type: 'value'
  },
  series: [{
    data: [120, 200, 150, 80, 70, 110, 130],
    type: 'bar'
  }]
});

const { chartRef } = useECharts({
  echarts,
  options: options.value,
  theme: 'light',
  autoResize: true
});
</script>

功能说明:

  1. 自动初始化:在组件挂载时自动初始化 ECharts 实例
  2. 响应式配置:使用 watch 深度监听 options 变化并自动更新图表
  3. 自动调整尺寸
    • 默认开启窗口 resize 监听
    • 使用防抖优化性能(可根据需要添加)
  4. 内存管理
    • 组件卸载时自动销毁实例
    • 移除事件监听器
  5. 类型安全
    • 完整的 TypeScript 类型定义
    • 支持 ECharts 原生类型

优化建议:

  1. 按需加载
// 在调用时按需引入
const { chartRef } = useECharts({
  echarts: await import('echarts/lib/echarts'), // 示例按需加载
  // ...其他配置
})
  1. 添加防抖
import { debounce } from 'lodash-es';

// 修改 handleResize
const handleResize = debounce(() => {
  if (!chartInstance.value) return;
  chartInstance.value.resize();
}, 300);
  1. 扩展功能
// 添加点击事件处理
const addChartClick = (handler: (params: any) => void) => {
  if (chartInstance.value) {
    chartInstance.value.on('click', handler);
  }
}

// 暴露更多控制方法
return {
  // ...其他返回值
  refresh: () => chartInstance.value?.resize(),
  dispose
}
  1. 错误处理增强
// 初始化时添加 try/catch
const initChart = () => {
  try {
    // 初始化代码
  } catch (err) {
    console.error('ECharts init failed:', err);
    // 可添加错误回调
    options.onError?.(err);
  }
}

这个 Hooks 封装了 ECharts 的核心功能,同时保持了良好的扩展性。使用时需要注意:

  1. 需要自行安装 echarts 依赖
  2. 按需引入需要的图表组件
  3. 建议配合 CSS 设置容器的宽高
  4. 复杂图表建议拆分 options 生成逻辑

可以根据具体项目需求继续扩展功能,如添加 loading 状态、主题切换、事件处理等。