大屏项目 vue3(二)
图表组件封装
将图表的初始化过程封装为一个组合函数useEcharts,最终返回一个 ref 给图表组件和 echarts 实例
ts
import * as echarts from "echarts";
import { onMounted, onUnmounted, ref, watch, type Ref } from "vue";
export const useEcharts = (options: Ref<echarts.EChartsCoreOption>) => {
const chartRef = ref<HTMLDivElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
const initChart = () => {
if (chartRef.value) {
chartInstance = echarts.init(chartRef.value);
chartInstance.setOption(options.value);
}
};
const resizeChart = () => {
if (chartInstance) {
chartInstance.resize();
}
};
//监听options变化
watch(
() => options.value,
(newVal) => {
if (chartInstance) {
chartInstance.setOption(newVal);
}
},
{ deep: true }
);
onMounted(() => {
initChart();
window.addEventListener("resize", resizeChart);
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
}
window.removeEventListener("resize", resizeChart);
});
return { chartRef, chartInstance };
};内存泄漏优化
TIP
summary 模式:
- Constructor:占用内存的资源类型
- Distance: 当前对象到根的引用层级距离
- Shallow Size: 对象所占内存(不包含内部引用的其它对象所占的内存)(单位:字节)
- Retained Size: 对象所占总内存(包含内部引用的其它对象所占的内存)(单位:字节)
compare 模式:
- New:新分配的内存空间数
- Deleted:销毁的内存空间数
- Delta:内存回收差值=新分配-销毁
背景:在客户现场运行了几天后页面突然崩溃了,本地短时间测试没有复现
排查:
- 先使用开发者工具的内存面板监控,发现在渲染大量图表时,页面内存占用不断上升,初步怀疑是 Echarts 图表渲染产生的内存泄漏,最终导致页面崩溃
- 基于这个猜测去排查了项目代码,发现确实有问题,代码里写了个定时器,每次刷新数据时,都会调用
init,加上销毁时 clear 和 dispose 方法使用不当,定时器循环重绘图表导致内存一直升高,最终页面崩溃。
优化:
- 只调用一次
echarts.init,通过 watch 合理使用 setOptions 更新图表内容,而不是每次都创建一个 echarts 实例 - 在组件卸载的时候用 dispose 代替 clear
clear 和 dispose
clear()
- 清空当前图表的配置项和数据,但不会销毁 ECharts 实例。
- 执行后,如果要重新绘制不需要重新 init
- 适合场景:需要保留实例,只是重新绘制内容。
dispose()
- 彻底销毁 ECharts 实例,释放 DOM 上的引用和事件监听器。
- 销毁后,如果要重新绘制,必须重新 echarts.init()。
- 适合场景:图表生命周期结束 or DOM 被移除。
错误代码
vue
<template>
<div class="chart-container">
<div ref="chartRef" class="chart"></div>
</div>
</template>
<script setup>
import * as echarts from "echarts";
import { ref, onMounted, onUnmounted } from "vue";
import axios from "axios";
const chartRef = ref(null);
let chartInstance = null;
let timer = null;
async function fetchDataAndRender() {
try {
const res = await axios.get("/api/chartData");
const data = res.data;
// ❌ 每次都重新 init —— 会创建多个 ECharts 实例
chartInstance = echarts.init(chartRef.value);
const option = {
title: { text: "实时数据" },
xAxis: { type: "category", data: data.time },
yAxis: { type: "value" },
series: [{ data: data.value, type: "line" }],
};
// 设置新数据
chartInstance.setOption(option);
} catch (e) {
console.error(e);
}
}
onMounted(() => {
// ❌ 每5秒拉取一次数据并重绘
fetchDataAndRender();
timer = setInterval(fetchDataAndRender, 5000);
});
onUnmounted(() => {
// ❌ 销毁时只 clear 而非 dispose,旧实例仍在内存中
if (chartInstance) {
chartInstance.clear(); // 只清空内容,不释放事件与内存
}
clearInterval(timer);
});
</script>
<style scoped>
.chart {
width: 100%;
height: 400px;
}
</style>