I have serious problems with memory leaks in a rather large Vue 3 application (TS, Vuex). It has a hierarchical structure with a large number of sub-components which amplifies the problems.
I managed to reproduce the issue in a very simple way and search your advice and opinion on that.
Reproduction: Create a fresh Vue 3 application with Vue CLI (4.5.15) with TypeScript & Babel (maybe not necessary, but similar to my application). Replace the Home.vue with the following code:
<template>
<div>
<button @click="clearArray">clearArray</button>
<button @click="fillArray">fillArray</button>
<div v-for="i in testArray" :key="i">{{ i }}</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
name: "LeakDetector",
beforeUnmount() {
this.clearArray();
},
setup() {
let testArray = ref<number[] | null>(null);
const clearArray = () => {
testArray.value = null;
};
const fillArray = () => {
testArray.value = Array.from({ length: 100000 }, (v, k) => k);
};
return { testArray, clearArray, fillArray };
},
});
</script>
I was thinking that Vue would take care of the onmounting/cleaning of regular HTML elements. But if I now
- click the fillArray button
- go to About page and back to Home page
the memory of those many div elememts cannot be freed by garbage collection. The call of clearArray in the beforeUnmount hook does not help. But if I
- click the fillArray button
- click the button clearArray and then
- change the route and back the garbage collection works.
I checked in chrome dev tools (memory tab) and I can find those detached elements there in case the array wasn't cleared. Also in Chrome Task Manager memory piles up quickly if the clearArray button isn't clicked before route change.
So:
- Am I doing / understanding / analysing something wrong?
- Is it necessary to do some "cleaning" for v-for-tags? (I didn't find anything in the vue docs)
- Is it a misbehavior / bug inside of vue?
UPDATE:
I tried a production build and the problem disappeared. That made me think of Vue.js devtools (6.0.0 beta 21) running in my browser. So I disabled that and it made the problem in the reproduction example disappear... I'll analyse that in the application and give a final update in case anybody else faces that issue. Thanks so far!
CONCLUTION:
I was on a wrong track there. The memory leak of the real application emerged from the following 2 things (and a bit of missing experience in interpreting the heaps):
- Usage of
const removeWatcher = this.$watch(...)which does not seem to remove the listeners when calling the removeWatcher() function. So replaced those with different functionality. I didn't debug that deeply. Might be another issue. - I forgot an .off() when using mitt for an EventBus.
