Vue 路由组件重复, 不释放的问题

Vue | vue-element-admin | 路由 | 不释放 | 不刷新

Posted by luoruiqing on June 11, 2020

在使用vue-element-admin 4.2.1 作为模板进行开发的时候, 发现该模板的缓存存在一些问题(未在官方示例下调试), 比如切换路由时不被释放的问题, 不知道最新版本是否存在, 这里记录一下影响最小的改动方法和思路.

Demo情况

路由组件:

  • ChartIndexView 路由组件 - 列表页面
  • ChartEditView 路由组件 - 编辑页面

模板:

  • keep-alive 缓存组件 (include内组件名称符合配置要求)
  • router-view 路由渲染组件

Vuex:

  • visitedViews 已打开标签页所有路由组件路由信息(每次新增或关闭都会维护该对象)
  • cachedViews 当前的路由组件的名称 name

AppMain.vue 如下

1
2
3
4
5
6
7
8
9
10
<template>
  <section class="app-main">
    <!-- 常规页面 -->
    <transition name="fade-transform" mode="out-in">
      <keep-alive :include="cachedViews">
        <router-view :key="key" />
      </keep-alive>
    </transition>
  </section>
</template>

开始

在顺序操作时并未发现问题, 且能正常释放路由:

img

此时使用Vue-DevTools看到的结果是, 没有问题.

img

开始进行关闭刷新 标签页操作后, 出现了问题

img

居然出现了重复路由组件, 并且key相同(这里很反直觉), 经过测试该路由组件也没有正常调用Vue生命周期销毁的方法, 这样会带来几个问题:

  • 缓存的页面不释放, 内存可能会持续增加
  • 不调用销毁的方法, 可能导致某些情况下业务场景不能被满足, 例如用户关闭该标签需要通知操作
  • 关闭后下次打开相同ID的路由 是否会触发多次create或都不触发creatd

查阅Vue官方文档后得到一个手动销毁当前实例的方法:

$destroy

完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。 触发 beforeDestroy 和 destroyed 的钩子

本着尽可能缩小代码影响的原则, 编写一个混合mixin如下:

1
2
3
4
5
6
7
8
9
10
export default {
    watch: {
        '$route'(val) {
            // 结合全局混合, 若无则使用当前路由
            if (!this.$store.state.tagsView.visitedViews.find(r => r.fullPath === val.fullPath)) {
                this.$destroy() // 手动触发销毁
            }
        },
    },
}

混合加入到需要释放的路由组件内即可

逻辑比较简单: 当路由发生改变的时候, 检测VuexvisitedViews, 也就是所有的标签页内是不是还有当前路由组件的路径, 如果不存在, 证明是用户关闭了, 可以手动释放该路由组件

看一下结果:

img

img

这样即便内存增加也是有释放机会的, 同时销毁行为也能正常执行, created也是每次使用每次触发 从代码看就能得出, 当路由切换时候才进行检测, 所以会有释放不够及时的问题, 如果需要及时释放, 则需setInterval配合一下:

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
export default {
    data() {
        // 缓存本组件真实的路由, 防止后续this.$route变更导致指定的对象错误
        const { path, fullPath, params, query } = this.$route ? this.$route : {}
        return {
            ROUTE: { path, fullPath, params, query },
        }
    },
    methods: {
        checkRouterDestroy() {
            // 结合全局混合, 若无则使用当前路由
            if (!this.$store.state.tagsView.visitedViews.find(r => r.fullPath === this.ROUTE.fullPath)) {
                window.clearInterval(this.ROUTE._EVENT) // 清除定时器
                this.$destroy() // 手动触发销毁
            }
        }
    },
    watch: {
        '$route': 'checkRouterDestroy',
    },
    cretaed() {
        // 生成定时器100毫秒检测一次是否释放
        this.ROUTE._EVENT = window.setInterval(this.checkRouterDestroy, 100)

    },
}

这样就可以做到及时释放路由组件, 附作用是需要100毫秒延迟, 目的是为了减小改动范围.