在uni-app中优雅地使用ECharts图表

1,695 阅读4分钟

前言

在uni-app应用开发中,数据可视化是一个常见需求。ECharts作为一款功能强大的开源可视化图表库,可以帮助我们轻松实现各种复杂的图表展示。本文将介绍如何在uni-app中封装一个通用的ECharts图表组件,实现高性能、低耦合的图表展示功能。

技术栈

  • uni-app:跨平台应用开发框架
  • Vue 3:渐进式JavaScript框架
  • ECharts:功能丰富的开源可视化图表库
  • lime-echart:uni-app的ECharts组件库

组件设计思路

我们的目标是封装一个通用的图表组件,具有以下特点:

  1. 易用性:使用简单的API,只需传入配置项即可渲染图表
  2. 响应式:自动响应配置变化和窗口大小变化
  3. 性能优化:合理使用Vue 3的新特性,如markRaw和防抖处理
  4. 资源管理:妥善处理组件生命周期,避免内存泄漏

组件实现

1. 组件结构

<script setup>
import * as echarts from 'echarts'
import { ref, computed, watch, onMounted, onBeforeUnmount, markRaw } from 'vue'

// 组件配置
defineOptions({
  name: 'PageEcharts',
  options: {
    styleIsolation: 'shared',
  },
})

// 组件属性
const props = defineProps({
  // 图表配置项
  options: {
    type: Object,
    default: () => ({})
  },
  // 图表高度
  height: {
    type: [String, Number],
    default: '300px'
  },
  // 图表宽度
  width: {
    type: [String, Number],
    default: '100%'
  },
})
</script>

<template>
  <l-echart ref="chartRef" :style="chartStyle"></l-echart>
</template>

2. 核心变量定义

// 图表DOM引用
const chartRef = ref(null)
// 图表实例
const chartInstance = ref(null)

// 使用计算属性监听配置变化
const options = computed(() => props.options)

// 计算样式
const chartStyle = computed(() => {
  const style = {}
  if (props.width) {
    style.width = typeof props.width === 'number' ? `${props.width}px` : props.width
  }
  if (props.height) {
    style.height = typeof props.height === 'number' ? `${props.height}px` : props.height
  }
  return style
})

3. 防抖函数实现

为了避免频繁的窗口大小变化导致图表过度重绘,我们实现了一个带取消功能的防抖函数:

// 防抖函数
const debounce = (fn, delay = 300) => {
  let timer = null
  const debounced = function(...args) {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => {
      fn.apply(this, args)
      timer = null
    }, delay)
  }
  
  // 添加取消方法,用于清理定时器
  debounced.cancel = () => {
    if (timer) {
      clearTimeout(timer)
      timer = null
    }
  }
  
  return debounced
}

// 窗口大小变化处理函数(添加防抖处理)
const resizeHandler = debounce(() => {
  if (chartRef.value) {
    chartRef.value.resize()
  }
}, 200)

// 注册窗口大小变化事件
uni.onWindowResize(resizeHandler)

4. 图表初始化

// 初始化图表
const initChart = () => {
  if (!chartRef.value) return
  const chartContainer = chartRef.value
  if (chartContainer) {
    // 初始化图表实例
    chartContainer.init(echarts).then(chart => {
      if (chart) {
        chartInstance.value = markRaw(chart)
        // 设置图表配置
        if (options.value && typeof options.value === 'object') {
          chartInstance.value.setOption(options.value, {
            notMerge: true,
          })
        }
      }
    }).catch(error => {
      console.error('初始化图表失败:', error)
    })
  }
}

注意这里使用了markRaw来包装ECharts实例,这是Vue 3中的一个重要优化,可以防止Vue将复杂的第三方库实例转换为响应式对象,从而提高性能。

5. 配置变化监听

watch(
  () => options.value,
  (options) => {
    if (chartInstance.value) {
      try {
        if (options && typeof options === 'object') {
          chartInstance.value.setOption(options, {
            notMerge: true,
          });
        } else {
          console.warn('图表配置更新无效或为空');
        }
      } catch (error) {
        console.error('更新图表配置失败:', error);
      }
    }
  },
  { deep: true },
);

6. 生命周期钩子

onMounted(() => {
  initChart()
})

// 组件销毁前清理资源
onBeforeUnmount(() => {
  console.log('PageEcharts组件销毁 - 开始清理资源')
  
  // 移除窗口大小变化监听
  uni.offWindowResize(resizeHandler)
  console.log('PageEcharts组件销毁 - 已移除窗口大小变化监听')
  
  // 取消防抖函数中的定时器
  if (resizeHandler.cancel) {
    resizeHandler.cancel()
    console.log('PageEcharts组件销毁 - 已取消防抖函数中的定时器')
  }
  
  // 销毁图表实例
  if (chartInstance.value) {
    chartInstance.value.dispose()
    chartInstance.value = null
    console.log('PageEcharts组件销毁 - 已销毁图表实例')
  }
  
  // 清空引用
  chartRef.value = null
  console.log('PageEcharts组件销毁 - 已清空DOM引用')
  
  console.log('PageEcharts组件销毁 - 资源清理完成')
})

组件使用示例

<template>
  <view>
    <page-echarts 
      v-if="showChart" 
      ref="chartRef" 
      :options="chartOptions" 
      height="750rpx"
    ></page-echarts>
    <view class="btn-container">
      <button @click="toggleData" class="toggle-btn">切换数据</button>
      <button @click="showChart = !showChart" class="toggle-btn" style="margin-left: 20rpx">
        {{ showChart ? '销毁图表' : '显示图表' }}
      </button>
    </view>
  </view>
</template>

<script setup>
import { ref, computed } from 'vue'

const showChart = ref(true) // 控制图表组件的显示与隐藏
const dataSetIndex = ref(0) // 用于跟踪当前数据集

// 定义数据集
const dataSets = [
  // 数据集1
  { /* ... */ },
  // 数据集2
  { /* ... */ }
]

// 切换数据函数
const toggleData = () => {
  dataSetIndex.value = (dataSetIndex.value + 1) % dataSets.length
}

// 使用计算属性定义图表配置
const chartOptions = computed(() => {
  const currentDataSet = dataSets[dataSetIndex.value]
  
  return {
    // 图表配置...
  }
})
</script>

性能优化技巧

  1. 使用markRaw处理ECharts实例:防止Vue的响应式系统对复杂对象进行代理,提高性能。

  2. 防抖处理窗口大小变化:避免频繁触发图表重绘,减少性能开销。

  3. 资源清理:在组件销毁时,确保清理所有事件监听器、定时器和图表实例,防止内存泄漏。

  4. 条件渲染:使用v-if控制图表组件的显示与隐藏,不需要时完全销毁组件,释放资源。

注意事项

  1. chartRef与chartInstance的区别

    • chartRef是对<l-echart>组件实例的引用
    • chartInstance是对ECharts实例的引用
    • 调用resize()方法时应使用chartRef.value.resize()
    • 设置图表配置时应使用chartInstance.value.setOption()
  2. 样式隔离:在小程序环境中,需要注意样式隔离问题,可以通过styleIsolation配置解决。

  3. 错误处理:在图表初始化和配置更新时添加错误处理,提高组件的健壮性。

总结

通过封装这个通用的ECharts图表组件,我们可以在uni-app项目中轻松实现各种复杂的数据可视化需求。组件设计充分考虑了易用性、响应式、性能优化和资源管理等方面,是一个实用的解决方案。

希望这篇文章对你在uni-app中使用ECharts有所帮助!