Vue.js使用ECharts内存泄漏问题

前段见时间有个项目,自己想尽可能查看现场各设备的运行参数,显示一些统计信息,之前同事用C#写了个画面的,但感觉不灵活,换个项目,要改的东西就很多,界面基本需要重画。后来想起之前用ECharts做过电子在屏的,应该展示效果会更好。之前考虑用React做前端的,后来想想就一个页面,直接用Vue干吧,正好Vue也升级到3了,然后用Electron做个包装,也不用再下浏览器。记录下碰到的问题吧。

ElementUI按需引入

之前Vue用的脚手架是vue-cli,统计时间是比较长的,升级到3之后,改用vite了,很方便。因为使用的组件不多,把ElementUI全部引入的话,JS文件会比较大,所以只引入使用的几个组件。参照官方文档

main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import 'element-plus/lib/theme-chalk/index.css'

import {
ElButton,
ElCol,
ElInputNumber,
ElProgress,
ElRow,
ElDescriptions,
ElDescriptionsItem,
} from 'element-plus';

const plugins = [
ElRow,
ElCol,
ElButton,
ElDescriptions,
ElDescriptionsItem,
ElInputNumber,
ElProgress
]

const app = createApp(App)

plugins.forEach(plugin => {
app.use(plugin)
})

app.config.globalProperties.$ELEMENT = { size: 'small', zIndex: 3000 }

app.mount('#app')

ECharts

按需引入

ECharts升级到5之后,与之前的引入有一点区别,具体可参考官方文档

linechart.vue
1
2
3
4
5
6
7
import * as echarts from 'echarts/core'
import { BarChart, LineChart } from 'echarts/charts'
import { CanvasRenderer } from 'echarts/renderers' // CanvasRenderer, SVGRenderer
import { GridComponent, TooltipComponent, TitleComponent } from 'echarts/components'
// require('echarts/theme/macarons') // echarts theme
import resize from './mixins/resize'
import moment from 'moment'

VUE中引入ECharts

最开始在VUE中引入ECharts老是报错,后来查了下,应该是DOM没有准备好,具体参考的应该是vue-element-admin里面的配置来着。应该用nextTick(),待DOM准备好之后,ECharts绑定到DOM。

LineChart.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
data() {
this.chart = null
return {
// chart: null
}
},
mounted() {
this.$nextTick(() => {
this.initChart()
})
},
methods: {
initChart() {
echarts.use([BarChart, LineChart, GridComponent, TooltipComponent, TitleComponent, CanvasRenderer])
this.chart = echarts.init(this.$el) // 因为这个组件只有一个div展示图片,所以用this.$el,如果是其它,可使用this.refs..$el
this.chart.setOption(
{
title: {
text: 'title'
},
grid: [
{
left: 50,
right: 50,
bottom: 0,
containLabel: true
},
],
axisPointer: {
link: {xAxisIndex: 'all'}
},
xAxis: [
{
// id: 'x1', 解决内存泄漏
type: 'category',
boundaryGap: true,
axisPointer: {
type: 'shadow'
},
splitLine: {
show: true,
interval: 9,
lineStyle: {
type: "dashed",
color: "rgba(0, 0, 0, 1)",
shadowBlur: 2
}
}
},
{
//id: 'x2',
type: 'category',
boundaryGap: true,
position: 'top',
axisLabel: {
show: false
}
}
],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'none',
animation: false,
}
},
yAxis: [
{
//id: 'y1',
type: 'value',
name: 'y1',
max: 900,
min: 0,
interval: 100,
axisLabel: {
formatter: '{value} °C'
},
axisTick: {
show: true
},
axisLine: {
show: true
},
splitLine: {
show: true
}
},
{
//id: 'y2',
type: 'value',
name: 'y2',
axisLabel: {
formatter: '{value} min'
},
axisTick: {
show: true
},
axisLine: {
show: true
},
splitLine: {
show: false
}
},
{
//id: 'y3',
type: 'value',
name: 'y3',
inverse: true,
min: 0,
show: false
}
],
series: [
{
//id: 's1',
name: 's1',
type: 'bar',
},
{
//id: 's2',
name: 's2',
type: 'line',
yAxisIndex: 1,
},
{
//id: 's3',
name: 's3',
type: 'bar',
xAxisIndex: 1,
yAxisIndex: 2,
}]
}
)
this.chart.on('click', 'series', params => {
this.$emit('click', parseInt(params.name))
})
},
}

内存泄漏

参照ECharts的样例做好了一个动态更新的图表,每秒从后台拉下数据进行更新,还蛮好看的。可是过两天后发现浏览器或者Electron内存暴了,直接把系统快干死。当时不清楚哪里的问题,以为是每秒获取数据后处理有问题,检查了好久都没办法解决,最后只好排除法,页面上的东西一个个排除观察一段时间看内存是不是一直往上涨,最后定位到ECharts。上网查了下,发现有提到的echarts.clear()echarts.dispose(),感觉这个有点不靠谱,这样子那不是每次echarts都需要重新生成?反正也试了,确实效果不好,图表都是重新生成,不能连续变化。

没办法,查文档吧,最后还真发现了些什么。里面有个组件合并模式,就是每次数据变化后都会调用一次this.echarts.setOption(),因为设置不当,数据一直往图表里面填,没有释放旧数据。其实解决办法很简单,就是把有变动的地方设置好ID,每次更新就更新带ID的数据。

LineChart.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
this.chart.setOption(
{
xAxis: [
{
id: 'x1',
data: xArray
},
{
id: 'x2',
data: xArray,
}
],
yAxis: [
{
id: 'y2',
max: Math.max(...stayTimes.filter(e => !isNaN(e))),
min: Math.min(...stayTimes.filter(e => !isNaN(e))),
},
{
id: 'y3',
max: Math.max(...stayTimesDelta.filter(e => !isNaN(e))) * 10,
}
],
series: [
{
id: 's1',
data: chargeTemps
},
{
id: 's2',
data: stayTimes
},
{
id: 's3',
data: stayTimesDelta
}]
}
)