vue多级菜单路由导致缓存keepalive失效

61次阅读

共计 1967 个字符,预计需要花费 5 分钟才能阅读完成。

一般的后台管理系统功能都比较繁多,存在有多级菜单的需求,但是在这种项目里往往 keep-alive 的表现却非常不稳定,有时候某个页面可以缓存,但是点几下就发现缓存丢了;有时候不知道怎么回事又死活不缓存了。

造成这个问题的原因是:多级路由组件嵌套。
具体分析:假如一个后台管理系统,有一个 main.vue 是所有页面的框架,里面有这样一段代码

          <keep-alive>
            <router-view v-if="$route.meta.keepAlive"></router-view>
          </keep-alive>
          <router-view v-if="!$route.meta.keepAlive"></router-view>

然后建立了一个父级菜单,页面是 ChildView.vue,里面的代码如下

<router-view></router-view>

假设这个父菜单里有两个子菜单,分别为 A 和 B,然后 A 设置为缓存 (keepAlive=true), B 不缓存(keepAlive=false);
当点击 A 菜单的时候,系统缓存了 ChildView.vue 组件。
当点击 B 菜单的时候,由于 B 设置的不缓存,所以导致 ChildView.vue 组件被销毁
这就是 keep-alive 为什么会失效的根本原因。

还有更复杂的,比如同一个菜单里的子菜单可以缓存,但是点击另外一个父菜单或者父菜单下的子菜单却不缓存,究其原理和上面的分析是一样的原因,就是多级菜单,多个共用组件导致的 keepAlive 缓存失效,keepAlive 根本没考虑到页面缓存的复杂性。

以下几种表现也是这个问题造成的原因之一:

  1. activated 和 deactivated 不触发
  2. 从 A 页面进入 B 页面发现的时候发现 A 页面的接口又会被重复触发调用

分析问题:
既然是多个 router-view 嵌套并且共用的情况下造成的,那么如果只存在一个 router-view,也就是只需要 main.vue 作为框架内所有页面的容器,就不会有这个问题。

实际上是不是多级菜单对于项目或者业务上来讲一点都不影响,只是界面显示上需要,让用户能更快点击到自己需要的功能页面而已。既然这样的话,显示的菜单保留多级的,实际的 router 弄成一级,将显示菜单和业务 router 分离开。

解决问题:
首先将配置好的多级 router 用 vuex 缓存起来,用作展示的菜单。
然后将 router 转换一下,转换成一级菜单,用 addRoutes 异步添加到 router 里面。

局部示例代码:

const formatRouter = (routes, newRoutes = []) => {
  routes.map(item => {if (item.children && item.children.length > 0) formatRouter(item.children, newRoutes);
    newRoutes.push(item);
  })
  return newRoutes;
}
let flatRoutes = formatRouter(routes);
router.addRoutes(flatRoutes);

然后面包屑导航要调整一下,大部分逻辑都是从 route.matched 里面获取的,但是现在 router 全是一级的,我们要从展示的菜单数据里面拿面包屑导航数据。

示例代码:

/**
 * 自定义查找字段, 根据最后一级某个字段查找完整树(整个父类)
 * @param {*} val     要查找对比的值
 * @param {*} data    要查找的数据
 * @param {*} fKey    要查找对比的字段
 */ 
const recursiveTreeByLastLevel = (val, data, fKey = 'value') => {let rData = [];
  for (let i = 0, len = data.length; i < len; i++) {rData.push(data[i]);
    if (data[i].children && data[i].children.length > 0) {rData = rData.concat(recursiveTreeByLastLevel(val, data[i].children, fKey));
      if (rData.some(item => item[fKey] === val)) return rData;
    }
    if (data[i][fKey] === val) return rData;
    rData = [];}
  return rData;
}
 
router.afterEach((to, from, next) => {var routerList = recursiveTreeByLastLevel(to.name, store.state.sidebarMenu, 'name')
  store.commit('setCrumbList', routerList) // 通过 vuex 缓存
})

搞完,收工!

正文完
 0