从源码来分析keep-alive多级路由缓存删除不掉的问题

我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!

起因

我们公司是主做toB的业务,常开发的是后台管理系统。从19年开始,大部分新开项目前端技术栈都从原jsp + layui +jquery变成vue全家桶 + elementui,新框架基于vue-element-admin-master二次开发。稳定上线一年多后,前一段客户突然反应,右键删除一个标签页后再打开还是缓存的上次的值,没有重新初始化,我测试了好多遍,发现问题并不总是重现

这是什么问题,黑人问号

发现问题

通过调试工具和vue devtools一点一点调试终于发现了这个bug,二级路由是正常缓存的,问题出在三级以上路由。

缓存的逻辑主要在layout下面的AppMain组件中

wastone.png
里面的cachedViews是在store中维护,记录路由的name值。

假设有两个三级路由xxx/#/a/b/cxxx/#/a/b/d
如果想要第三级c和d缓存,常用的方案就是把b也加入到keep-alive组件的include参数中,也就是include的值为['b', 'c', 'd']
上面两个路由顺序点开后,通过Vue Devtools中的Computent里面看到的结构应该是

-Root
 -App
  -Layout
   -Navbar
   -Sidebar
   -TagsView
   -AppMain
    -b      [inactive]
      -c    [inactive]
    -b
      -d
    -keep-alive
复制代码

现在点击路由xxx/#/a/b/d标签的删除是删除不了d的缓存。

从keep-alive源码的逻辑看(位置在vue源码中的src/core/components/keep-alive.js


function matches (pattern: string | RegExp | Array<string>, name: string): boolean {
  if (Array.isArray(pattern)) {
    return pattern.indexOf(name) > -1
  } else if (typeof pattern === 'string') {
    return pattern.split(',').indexOf(name) > -1
  } else if (isRegExp(pattern)) {
    return pattern.test(name)
  }
  /* istanbul ignore next */
  return false
}

// 删除缓存
function pruneCache (keepAliveInstance: any, filter: Function) {
  const { cache, keys, _vnode } = keepAliveInstance
  for (const key in cache) {
    const cachedNode: ?VNode = cache[key]
    if (cachedNode) {
      // 主要问题在这个地方 name总是取的二级路由组件的名称,导致删除不掉
      const name: ?string = getComponentName(cachedNode. componentOptions)
      // 本例子name获取的是b  因为filter(name)为true 所以就没有走下面的删除缓存逻辑
      if (name && !filter(name)) {
        pruneCacheEntry(cache, key, keys, _vnode)
      }
    }
  }
}

mounted () {
  // 在初始化后监听include的变化,决定是否删除缓存
  this.$watch('include', val => {
    pruneCache(this, name => matches(val, name))
  })
}

复制代码

当点击标签上的刷新时,会把include数组中的d删除,但是inactive状态缓存d并没有被删掉,原因就在于触发include监听后,pruneCache中 const name: ?string = getComponentName(cachedNode.componentOptions)获取到的name为b,因为b还存在,就无法删除对应的缓存d。

解决问题

问题只要找到,再寻找解决方案就容易许多。所以最终选择的解决方案

  • 方法一:参考一位大佬的方法(看了之后忘了保存网址)。因为二级路由是没有这个问题的,所以把数据整体改为二级路由,菜单使用原多级数据,addRoute数据改为二级路由(亲测可以解决)
// 原始数据用于左侧菜单展示使用
const nestedRouter = {
  path: '/test',
  component: Layout,
  redirect: '/test/menu1/menu1-1',
  name: 'test',
  children: [
    {
      path: 'menu1',
      component: () => import('@/views/test/menu1/index'), // Parent router-view
      name: 'Menu1',
      redirect: '/test/menu1/menu1-1',
      children: [
        {
          path: 'menu1-1',
          component: () => import('@/views/test/menu1/menu1-1'),
          name: 'Menu1-1'
        },
        {
          path: 'menu1-2',
          component: () => import('@/views/test/menu1/menu1-2'),
          name: 'Menu1-2'
        }
      ]
    }
  ]
}

// 通过递归处理后的数据 可以用于addRoute使用
const nestedRouter = {
  path: '/test',
  component: Layout,
  redirect: '/test/menu1/menu1-1',
  name: 'test',
  children: [
    {
      path: 'menu1-1',
      component: () => import('@/views/test/menu1/menu1-1'),
      name: 'Menu1-1'
    },
    {
      path: 'menu1-2',
      component: () => import('@/views/test/menu1/menu1-2'),
      name: 'Menu1-2'
    }
  ]
}
复制代码
  • 方法二:由源码入手

关键点在于如何匹配到想要删除的Cache,从现在看来想要从源码上实现可以修改匹配条件,只能临时解决问题,但是想要所有的情况都适用这条路不好实现,可能尤大设计的就是很简单的模式,没有考虑这么复杂的场景。如果觉得有必要可以自己写一个keep-alive组件。

有哪些写的不好,欢迎各位大佬指正

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享