vue中使用keep-alive动态删除已缓存组件方式

目录
  • 项目场景
  • 问题描述
  • 解决方案

项目场景

在做后台管理系统的时候,有这样一个需求:

后台的界面如下:

点击左边的菜单,会在右边的顶部生成一个个tag导航标签。当打开多个tag页时,用户可以在多个tag之间进行切换。需要在新增,修改页面切换tag时候,保留之前的信息,不进行页面的刷新。

问题描述

经过查询vue文档,可以使用keep-alive实现标签路由缓存,实现方式如下:

在路由配置的meta中添加keepAlive,如下:

{
    path: '/actdebt',
    component: Layout,
    redirect: '/actdebt/add',
    children: [
      {
        path: 'add',
        name: 'XXX新增配置',
        meta: {
          keepAlive: true,
        },
        component: () =>
            import (/* webpackChunkName: "page" */'@/views/bankact/actdebt/add')
      }]
  },

然后在页面中使用v-if做判断,并且加上key

<keep-alive>
   <router-view :key="$route.fullPath" class="avue-view"
    v-if="$route.meta.keepAlive" />
</keep-alive>
   <router-view class="avue-view" v-if="!$route.meta.keepAlive" />

使用上面这种方式解决了修改不同记录的缓存问题,因为不同记录的fullPath 不一样,这样的话key就会不一样。

但是对于新增和修改同一条记录还是有缓存问题。例如新增一条记录保存成功后,下次在打开新增页面,还是缓存有之前的记录。

修改页面也是的,修改同一条记录保存成功后,再次打开可能还是会有之前的修改数据。

解决方案

要解决上面这种问题我想到的解决方案为:在不同的tag导航栏切换的时候,保留缓存数据。当关闭tag导航栏或者关闭页面的时候,清除缓存。

清除缓存可以在组件里面的deactivated钩子函数调用this.$destroy();但是发现下次打开这个页面的时候,新的组件不会被缓存了。

可以利用keep-alive的include,新打开标签时,把当前组件名加入到include数组里,关闭标签时从数组中删除关闭标签的组件名就可以了。

Include里面的值必须和组件的name属性保持一致,如下:

但是如果我同一个组件加载了两次,一个需要缓存,一个不需要缓存。但是他们的name却是一样的,还是无法解决问题。

所以是否可以重写keep-alive源码,使include可以按照路由地址匹配,而不是按照组件的name匹配。完整的代码如下:

新建keepAlive.js文件

/**
 * base-keep-alive 主要解决问题场景:多级路由缓存
 * 前提:保证动态路由生成的route name 值都声明了且唯一
 * 基于以上对keep-alive进行以下改造:
 *   1. 组件名称获取更改为路由名称
 *   2. cache缓存key也更改为路由名称
 *   3. pruneCache
 */
 const _toString = Object.prototype.toString
 function isRegExp(v) {
   return _toString.call(v) === '[object RegExp]'
 }
 export function remove(arr, item) {
   if (arr.length) {
     const index = arr.indexOf(item)
     if (index > -1) {
       return arr.splice(index, 1)
     }
   }
 }
 /**
  * 1. 主要更改了 name 值获取的规则
  * @param {*} opts
  */
 function getComponentName(opts) {
   // return opts && (opts.Ctor.options.name || opts.tag)
   return this.$route.path
 }
 function isDef(v) {
   return v !== undefined && v !== null
 }
 function isAsyncPlaceholder(node) {
   return node.isComment && node.asyncFactory
 }
 function getFirstComponentChild(children) {
   if (Array.isArray(children)) {
     for (let i = 0; i < children.length; i++) {
       const c = children[i]
       if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
         return c
       }
     }
   }
 }
 function matches(pattern, name) {
   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, filter) {
   const { cache, keys, _vnode } = keepAliveInstance
   for (const key in cache) {
     const cachedNode = cache[key]
     if (cachedNode) {
       // ------------ 3. 之前默认从router-view取储存key值, 现在改为路由name, 所以这里得改成当前key
       // const name = getComponentName.call(keepAliveInstance, cachedNode.componentOptions)
       const name = key
       if (name && !filter(name)) {
         pruneCacheEntry(cache, key, keys, _vnode)
       }
     }
   }
 }

 function pruneCacheEntry(
   cache,
   key,
   keys,
   current
 ) {
   const cached = cache[key]
   if (cached && (!current || cached.tag !== current.tag)) {
     cached.componentInstance.$destroy()
   }
   cache[key] = null
   remove(keys, key)
 }

 const patternTypes = [String, RegExp, Array]

 export default {
   name: 'keep-alive',
   // abstract: true,
   props: {
     include: patternTypes,
     exclude: patternTypes,
     max: [String, Number]
   },

   created() {
     this.cache = Object.create(null)
     this.keys = []
   },

   destroyed() {
     for (const key in this.cache) {
       pruneCacheEntry(this.cache, key, this.keys)
     }
   },

   mounted() {
     this.$watch('include', val => {
       pruneCache(this, name => matches(val, name))
     })
     this.$watch('exclude', val => {
       pruneCache(this, name => !matches(val, name))
     })
   },

   render() {
     const slot = this.$slots.default
     const vnode = getFirstComponentChild(slot)
     const componentOptions = vnode && vnode.componentOptions
     if (componentOptions) {
       // check pattern
       const name = getComponentName.call(this, componentOptions)
       // ---------- 对于没有name值得设置为路由得name, 支持vue-devtool组件名称显示
       if (!componentOptions.Ctor.options.name) {
         vnode.componentOptions.Ctor.options.name
       }
       const { include, exclude } = this
       if (
         // not included
         (include && (!name || !matches(include, name))) ||
         // excluded
         (exclude && name && matches(exclude, name))
       ) {
         return vnode
       }

       const { cache, keys } = this
       // ------------------- 储存的key值, 默认从router-view设置的key中获取
       // const key = vnode.key == null
       //   ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
       //   : vnode.key

       // ------------------- 2. 储存的key值设置为路由中得name值
       const key = name

       if (cache[key]) {
         vnode.componentInstance = cache[key].componentInstance
         // make current key freshest
         remove(keys, key)
         keys.push(key)
       } else {
         cache[key] = vnode
         keys.push(key)
         // prune oldest entry
         if (this.max && keys.length > parseInt(this.max)) {
           pruneCacheEntry(cache, keys[0], keys, this._vnode)
         }
       }
       vnode.data.keepAlive = true
     }
     return vnode || (slot && slot[0])
   }
 }

然后在main.js中引入该组件,使组件可以全局使用

在页面直接使用BaseKeepAlive:

 <BaseKeepAlive :include="cachetags">
            <router-view :key="$route.fullPath" class="avue-view" />
          </BaseKeepAlive>

cachetags 方法就是新打开标签时,把当前组件名加入到include数组里,关闭标签时从数组中删除关闭标签,源码如下:

computed: {
    ...mapGetters(['isLock',  "tagList",'isCollapse', 'website']),
    cachetags(){
      let list=[]
      for(let item of this.tagList){
        if(!validatenull(item.keepalive)&&item.keepalive){
        list.push(item.value)
        }

      }
    return list.join(',')
    }
    },

方法中的tagList就是导航栏当前打开相应的tag集合如下图所示

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 图文详解keep-alive如何清除缓存

    目录 开篇场景 使用v-for直接渲染 直接用自定义组件渲染 keep-alve 强大的缓存存在的问题 总结 开篇场景 我的项目是前后端没有分离的,技术栈是layui+vue+jq,vue是我后面引入的,为了能够更好的组件化,将页面组件化,减少代码重复量,因为jq的页面需要经常获取dom 元素,导致页面非常的冗余,jq 的页面也不好做组件化,将页面的功能抽离封装,所以我引入了vue,最近需要是实现一个需求,就是多页面缓存,通过点击左边的菜单栏,右边展示页面,但是需要将点击过的页面都进行缓存,如下

  • vue keepAlive缓存清除问题案例详解

    vue项目中经常会用到keepalive来做缓存,在应付基本要求上可以说非常方便.但是遇到同一个页面,根据条件不同,分别缓存或者不缓存,就有些麻烦了. 首先先把坑列出来: 1. <keep-alive v-if="xxx"> <router-view /> </keep-alive> <keep-alive v-else> <router-view /> </keep-alive> 网上很多都是这种方法,用了这种方

  • vue使用keep-alive后清除缓存的方法

    什么是keepalive? 在平常开发中,有部分组件没有必要多次初始化,这时,我们需要将组件进行持久化,使组件的状态维持不变,在下一次展示时,也不会进行重新初始化组件. 也就是说,keepalive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染 .也就是所谓的组件缓存 基本用法 <keep-alive> <component /> //你的组件 </keep-alive> 需求:从列表页进入详情页,再返回列表页时保留查询条件,但在切换其他tab

  • vue里如何主动销毁keep-alive缓存的组件

    问题产生的背景 我们一个后台,在切换一些标签页的时候,是使用的 keep-alive 缓存的标签页,也使用了 include 属性来决定哪个页面进行缓存,而标签页的切换实际上是路由的切换,也就是说打开一个新标签页的时候,url 会跟着变化,老的标签页如果在 keep-alive 的 include 范围内那就会缓存下来. 然后客服人员就反馈页面开的久了就会崩溃,因为他们基础上不会刷新页面(工作需要),又总有切换标签的习惯,最后导致内存越来越大最后崩溃. 依赖环境 这个项目是基于一个开源 vue

  • vue中使用keep-alive动态删除已缓存组件方式

    目录 项目场景 问题描述 解决方案 项目场景 在做后台管理系统的时候,有这样一个需求: 后台的界面如下: 点击左边的菜单,会在右边的顶部生成一个个tag导航标签.当打开多个tag页时,用户可以在多个tag之间进行切换.需要在新增,修改页面切换tag时候,保留之前的信息,不进行页面的刷新. 问题描述 经过查询vue文档,可以使用keep-alive实现标签路由缓存,实现方式如下: 在路由配置的meta中添加keepAlive,如下: { path: '/actdebt', component: L

  • vue中设置、获取、删除cookie的方法

    1.在src目录下的access下新建一个cookie.js, 内容如下: export function setCookie(c_name,value,expire) { var date=new Date() date.setSeconds(date.getSeconds()+expire) document.cookie=c_name+ "="+escape(value)+"; expires="+date.toGMTString() //console.lo

  • 关于vue中根据用户权限动态添加路由的问题

    根据用户的权限,展示不同的菜单页. 知识点 路由守卫(使用了前置守卫):根据用户角色判断要添加的路由 vuex:保存动态添加的路由 难点 每次路由发生变化时都需要调用一次路由守卫,并且store中的数据会在每次刷新的时候清空,因此需要判断store中是否有添加的动态路由. (若没有判断 则会一直添加 导致内存溢出) 根据角色判断路由 过滤动态路由 判断每条路由角色是否与登录传入的角色一致 <template> <div> <el-menu :default-active=&q

  • vue中使用echarts实现动态数据绑定以及获取后端接口数据

    目录 前言 1.柱状图 2.折线图 3.饼状图 4.地图 总结 前言 之前几篇echarts的文章是实现了静态的柱状图.折线图.饼状图.地图,在项目中我们肯定是需要获取后端接口,将后端返回的数据显示在图表上,所以这次就记录一下如何实现echarts的动态数据绑定. 简单来讲,就是从接口获取到的数据,需要在图表的方法里再次定义一下,然后用setOption方法就可以获取到了. 1.柱状图 首先看接口传过来的数据,传过来一个数组,第一条年度2021,数量1,第二条年度2022,数量3 因为柱状图的数

  • vue中移动端调取本地的复制的文本方式

    我就废话不多说了,大家还是直接看代码吧~ _this.$vux.confirm.show({ title: '复制分享链接', content: '分享的内容', onConfirm() { // _this.$vux.toast.text('复制成功') let url = '分享的内容'; let oInput = document.createElement('input'); oInput.value = url; document.body.appendChild(oInput); o

  • 基于vue中css预加载使用sass的配置方式详解

    1.安装sass的依赖包 npm install --save-dev sass-loader //sass-loader依赖于node-sass npm install --save-dev node-sass 2.在build文件夹下的webpack.base.conf.js的rules里面添加配置,如下红色部分 { test: /\.sass$/, loaders: ['style', 'css', 'sass'] } <span style="color:#454545;"

  • vue中提示$index is not defined错误的解决方式

    今天学习Vue中遇到了一个报错信息:$index is not defined,是我写了个for循环在HTML中,然后是因为版本的问题 下面是解决方法: 原来的是 v-for="person in items" v-on:click="deletePerson($index)"//这个仅仅适用于1.0版本,不要采坑了同学们 这个在Vue1.0版本中式适用的可以直接使用$index,但是在2.0是不适合的 在Vue 2.0版本中获取索引我们需要通过 v-for = &

  • Vue 中如何将函数作为 props 传递给组件的实现代码

    本文 GitHub https://github.com/qq44924588 ... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料.欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西. Vue 新手经常问的一个常见问题.可以将字符串.数组.数字和对象作为props传递.但是你能把一个函数当作一个props来传递吗? 虽然可以将函数作为props传递,但这种方式不好.相反,Vue 有一个专门为解决这问题而设计的功能,接下来,我们来看看. 向组件传入函数 获

  • vue项目keepAlive配合vuex动态设置路由缓存方式

    目录 需求 效果图 解决方案 1.App.vue文件 2.main.js文件 3.store/modules/common.js文件 4.utils/utils.js文件 5.store/index.js文件 6.router/index.js文件 7.routers/Home.vue文件 需求 首页 → 列表页→ 详情页(缓存列表页面 ) → 列表页(不重新加载列表页)→ 首页(清除列表页的缓存) 效果图 解决方案 直接使用keepAlive会出现一个问题,当从查询1进入列表页面,这时缓存li

  • Vue中使用 Aplayer 和 Metingjs 添加音乐插件的方式

    目录 1.Aplayer和Metingjs 的文档 2.使用方式 3.完整API 4.总结 1.Aplayer和Metingjs 的文档 Aplayer官网文档 Metingjs官网文档 2.使用方式 在 public 目录下的 index.html 中引入核心依赖 <link rel="stylesheet" href="http://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.css" rel="e

随机推荐