移除渲染组件时 Vue 内存泄漏

Sve*_*ort 8 javascript memory-leaks memory-management vue.js vuex

在 Vue 应用程序中,我遇到内存泄漏,发生的情况如下:

  • 我们有一个在 v-for 中渲染的组件,其中包含许多子组件
  • 当从数组中删除相应的元素时,v-for 会重新渲染这些组件,并正确删除与从数组中删除的元素相对应的组件。

然而,分配的内存永远不会被释放,应用程序一开始的 RAM 使用量约为 30-40 MB,当渲染 v-for 时,RAM 使用量会增加到 200MB(最终会增加到超过 1GB,当更多元素时,浏览器会崩溃)添加或切换时)。当元素被删除时,它稳定地保持在 200MB(即使在手动垃圾收集时),所以它看起来像是保留了我的组件。

我尝试使用堆快照来定位问题,但它只显示一个子组件作为保留器。我无法找到导致该组件不被垃圾收集的原因。我已尝试取消订阅根上的所有事件侦听器,this.$root.off但这似乎根本没有帮助......

代码本身是保密的,所以我不能只是分享它,但是如果需要一些代码来理解问题,请告诉我,以便我可以提供一个复制的示例。

有谁知道如何解决这个问题,或者有任何想法如何找到内存泄漏的原因?

更新

这是在 v-for 中渲染组件的组件:

<template>
    <b-tabs card class="tabMenu" v-model="index">
        <b-tab v-for="(tab) in tabs" @click="doSomething" @change="doSomething">
                <TabComponent :tab="tab"></TabComponent>
        </b-tab>
    </b-tabs>
</template>

<script>
    import TabComponent from "./TabComponent";

    export default {
        components: {
            TabComponent,
        },
        created: function () {
            this.$root.$on("addTab", this.addTab);
        },
        data: function () {
            return {
                tabs: this.$store.state.tabs,
            }
        },
        beforeDestroy: function(){             
            this.$root.$off("addTab");

        },
        methods: {
            addTab(tab) {
                this.$store.commit("addTab", {tab: tab});
            },
        }
    };
</script>
Run Code Online (Sandbox Code Playgroud)

以及它呈现的选项卡组件:

<template>
    <div @mousedown.stop>
    <!--   Other components are loaded here but not relevant    -->
        <div>

                <div v-show="conditionA">
                    <resize-observer @notify="doSomething" v-if="conditionC"></resize-observer>

<!--          This component renders many SVG elements which can be found in the heapsnapshot as DetachedSvgElements when the parent is not present anymore          -->
                    <VisualizationComponent v-show="conditionD"
                                           :tab="tab"></VisualizationComponent>
                </div>
        </div>
    </div>
</template>

<script>
    export default {
        components: {

            },
        props: {
            tab: TabObject,
        },
        data: function () {
            return {

            }
        },
        watch: {
           // Some watchers
        },
        mounted: function () {
            this.$nextTick(function () {
                // Do some calculations
                this.$root.$emit("updateSomething");
            });
        },
        created: function(){
            this.$root.$on("listen", this.doSomething);
            // And listen to more events
        },
        beforeDestroy: function(){
            this.$root.$off("listen");
            // And unsubscribe all others
        },
        computed: {
            // Quite a lot of computed props
        },
        methods: {
            // And also many methods for data processing
        }
    }
</script>
Run Code Online (Sandbox Code Playgroud)

小智 7

我有类似的问题。在我的例子中,我通过属性传递给下一个组件的对象既复杂又大,我不知道你是否也是这种情况?

通过改变传递对象的方式解决了我的问题。通过将属性更改为数字(在本例中为 ID),我能够在使用该属性的组件中检索我的对象(基于 ID)。因此,我不必重复传递整个对象。由于某种原因,将大对象作为数据道具传递并不能正常工作,并会导致奇怪的行为......

在您的情况下,当您不将“tab”属性直接传递给组件,而是传递该元素在存储中的位置索引,然后直接从组件内的存储中获取它时,这可能会有所帮助。

所以你需要将你的 v-for 更改为:

<b-tab v-for="(tab, index) in tabs" @click="doSomething" @change="doSomething">
    <keep-alive>
        <TabComponent :tabIndex="index"></TabComponent>
    </keep-alive>
</b-tab>
Run Code Online (Sandbox Code Playgroud)

在你的 TabComponent 中:

props: {
    tabIndex: Number,
},
data: function () {
    return {
        tab: this.$store.state.tabs[this.tabIndex]
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,这一原则也需要应用于执行相同操作的任何子组件,以防止子组件中出现任何内存泄漏,这显然也会影响父组件。希望我能帮助你:)