vue keep-alive使用,多标签保留缓存,刷新、删除、复制标签,缓存销毁,缓存爆栈解决

7 篇文章 0 订阅
2 篇文章 0 订阅
目标
  • 页面打开记录展示在页面头部,每个标签都会保留上一次的操作缓存
  • 头部标签支持删除,刷新,复制
  • 同一个页面(一个.vue生成新的路由)生成多个标签时每个都能保留独立缓存
效果

重复打开关闭页面后的内存曲线(chrome的控制台 - More tools/Performance monitor),曲线为锯齿形,说明缓存是正常创建和销毁了
在这里插入图片描述

方案
缓存销毁
  • vue内部的include和exclude可以实现静态路由的销毁,静态路由指代码运行起来前创建的路由
  • 动态路由的话通过更改key值可以刷新掉缓存,但上一个key对应的缓存数据没有自动清除,需要手动清理
路由配置
// 第一种
<keep-alive>
	<router-view v-if="$route.meta.keepAlive" :key="$route.meta.timeKey"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>

// 第二种
<keep-alive :include="arrRouterAlive">
    <router-view :key="$route.meta.timeKey"/>
</keep-alive>
// arrRouterAlive为要保留缓存的组件name的集合
// timeKey为实现刷新的标识值
同一个表单页通过不同的配置生成多个路由
/**
* 在路由的beforeRouter内写逻辑
* 判断是否进入到了需要创建路由的组件内,进入的话就重新添加路由
* 要考虑用户访问新生成的路由的链接的情况
* 不能每次都是新生成路由,因为router只有添加路由的方法,没有删除路由的方法,已经创建的并且已经关闭的需要重复利用
* store需要存储要删除的路由removeCacheName: {}
*/

// 几个公用方法
class routerMethods{
    // 获取唯一值
    getOnlyKey() {
      ...
      return onlyKey;
    }
}
const RouterMethods = new routerMethods()

// 路由配置
{
    path: 'testEdit',
    name: 'editTransfer',
    component: editComponents,
    meta:{
    	// true 基础路由,检测到需要生成新的路由
        isTransfer: true,
        // 用来判断新生成的并且被关闭的路由时使用
        _name: 'editTransfer',
        // 路由唯一标识置,可以在路由export时统一设置
        timeKey: RouterMethods.getOnlyKey() 
    }
},
// 这里要注意,keepAlive对于多级路由的兼容性不好,所以不要超过2级或者统一转换成一级

// beforeEach加入判断路由生成逻辑
router.beforeEach((to, from, next) => {
    ...
    let { name, path, meta } = to;
    // 获取是否需要转发
    const isTransfer = meta && meta.isTransfer;
    // 要跳转的基础路由配置name
    let redirectRouterName = '';
    // 要添加路由的path
    let redirectRouterPath = '';
    // 要生成的路由标识
    let onlyKey = RouterMethods.getOnlyKey();
    // 是否需要重新添加路由
    let isAddRouter = false;
    // 检测到name为需要生成路由的name
    if(isTransfer){
    	/**
        * 转发路径,需要添加路由到router内
        * _redirect_ + 路由名称 做标识,可以在直接访问路径时解析出原始name
        */ 
        isAddRouter = true;
        redirectRouterName = name;
        redirectRouterPath = `${path}/_redirect_${name}/`;
    } else if(path.indexOf('_redirect_') > -1){
        // 被转发的新路径
        let metaName = meta && meta._name;
        // 不存在meta的name时则为通过链接访问的,需要添加路由
        if(!metaName){
        	isAddRouter = true;
            // 解析缓存路由name
            let arrNameSplit = path.match(/\/_redirect_\S*\//)[0].split('/');
            redirectRouterName = arrNameSplit[arrNameSplit.length - 2].split('_redirect_')[1];
            redirectRouterPath = `${path.match(/\S*_redirect_\S*\//)[0]}`;
            // 解析path的标识值
            onlyKey = path.match(new RegExp(`_redirect_${redirectRouterName}\/\\S*`))[0].split('/')[1];
        }
    }
    
    if(isAddRouter){
        // 是否存在可重复利用路由,跳转路由的原始组件名字需要在已存在路由中存在(保证传值等配置相同)
        let closeRouter = router.options.routes.find(a => {
	    	return a.meta && a.meta.closed && a.meta._name === redirectRouterName
        })
        // 存在的话直接跳转到那个路由去
        if(closeRouter){
            // 获取关闭路由的标识值
            closeRouter.meta.closed = false;
            next({
                name: closeRouter.name
            })
        } else {
            // 关闭当前跳转,重新添加并跳转
            next(false)
            
            let itemRouter = router.options.routes.find(a => {
            	return a.name === redirectRouterName
            })

            let meta = {
                ...itemRouter.meta,
                timeKey: onlyKey,
                _name: redirectRouterName
            }
            // 去除meta的isTransfer属性
            delete meta.isTransfer;
            
            let addRouterName = `_redirect_${redirectRouterName}/${onlyKey}`
            let addItem = [{
	            ...itemRouter,
	            name: addRouterName,
	            path: `${itemRouter.path}/_redirect_${redirectRouterName}/${onlyKey}`,
	            // 这里要注意,新生成router的components需要指向从已存在路由
	            component: itemRouter.component,
	            meta
            }]
            // 这里要注意,router新增后options.routes不会自动添加,需要手动加进去
            router.options.routes = router.options.routes.concat(addItem)
            router.addRoutes(addItem)
            next({
                path: `${redirectRouterPath}${onlyKey}`
            })
        }
    } else {
        next(true)
    }
    ...
})
销毁缓存路由
/**
* 通过更改router-view的key值可以刷新组件,但是上一个key的缓存还是会被保留下来,打开很多个页面后就会爆栈,所以需要把历史key对应缓存销毁掉,用到的vue api就是$destroy
* store内存一分最新的timeKey,然后在缓存组件的生命周期内监听timeKey数据变化,变化后去比对timeKey,除了最新的timeKey组件保留外,其他的销毁(被缓存的组件生命周期还是会执行)
*/

// 定义mixin,需要缓存的页面引入
data(){
    // 路由的keepAlive
    __routeKeepAlive: false,
    // 路由的timeKey
    __routeTimeKey: null,
    // 路由的name
    __routeName: null,
    // 原始name值
    __route_name: null
},
mounted() {
    let route = this.$route
    if (route) {
        // route keepAlive
        this.__routeKeepAlive = !!route.meta.keepAlive;
        // route timeKey
        this.__routeTimeKey = route.meta.timeKey;
        // route name
        this.__routeName = route.name;
        // route _name
        this.__route_name = route.meta._name;
    }
},
computed: {
    // tag项
    __navHisList() {
      return this.$store && this.$store.state._navHisList
    },
    // 要删除的路name对象
    removeCacheName() {
      return this.$store.state.removeCacheName;
    },
},
methods: {
	销毁缓存方法独立出来
	// 处理销毁缓存
	destroyVueItem() {
	  if (!this.__routeKeepAlive) return;
      // 逐级强制删除cache,这里就是强制清除缓存值的逻辑
      if (this.$vnode && this.$vnode.data.keepAlive) {
        if (this.$vnode.parent && this.$vnode.parent.componentInstance && this.$vnode.parent.componentInstance.cache) {
          if (this.$vnode.componentOptions) {
            var key = this.$vnode.key == null
              ? this.$vnode.componentOptions.Ctor.cid + (this.$vnode.componentOptions.tag ? `::${this.$vnode.componentOptions.tag}` : '')
              : this.$vnode.key;
            var cache = this.$vnode.parent.componentInstance.cache;
            var keys = this.$vnode.parent.componentInstance.keys;
            if (cache[key]) {
              if (keys.length) {
                var index = keys.indexOf(key);
                if (index > -1) {
                  keys.splice(index, 1);
                }
              }

              delete cache[key];
            }
          }
        }
      }

      this.$destroy();

      let __routeName = this.__routeName;
      // 去除后的反馈,反馈是要加的,清除路由后把store内存储的待清除路由key清理掉
      this.$store.commit('setCacheNameRemoveResolve', __routeName);
	}
},
watch: {
    // 监听需要删除的name,处理删除操作
    removeCacheName(cacheName) {
      if (!this.__routeKeepAlive) return;
      let __routeName = this.__routeName;
      cacheName && cacheName.hasOwnProperty(__routeName) && this.destroyVueItem();
    },
}
刷新标签路由
/**
* 先更改当前路由的meta.timeKey,然后同步该timeKey到store内
* 已缓存的组件会比对timeKey来判断是否销毁
*/
refreshPage(item) {
	// 更改路由的缓存
	let router = this.$router;
	let r_name = item.name;
	let targetRouter = null;
  	router.options.routes.some(a => {
    	let exit = false;
    	if (a.name === r_name) {
     		targetRouter = a;
      		exit = true;
   		 } else if (a.hasOwnProperty('children')) {
      		a.children.some(b => {
	        	if (b.name === r_name) {
	          		targetRouter = b;
	          		exit = true;
	          		return true
	        	}
      		});
   	 	}

    	if (exit) return true;
  	});

  	if (targetRouter) {
		const onlyKey = 随机值;
      	target.meta.timeKey = onlyKey;
      	this.$store.commit('setCacheNameRemovePending', targetRouter.name);
	}
},
删除标签路由
/**
* 删除的话就是删除store的arrTagRouters内的当前项
* 然后更改router.options.routes内的组件meta.timeKey,更改后被缓存的组件会走销毁流程
* 这里要注意:被删除的组件meta.closed要设置为true,保留路由的重复利用
*/
clickNaviHisDel(item) {
	// 删除store的保存标签的匹配数据
	...
      // 更改 清除关闭路由的缓存
	let router = this.$router;
	let r_name = item.name;
	let targetRouter = null;
	router.options.routes.some(a => {
		let exit = false;
		if (a.name === r_name) {
			  targetRouter = a;
			  exit = true;
		} else if (a.hasOwnProperty("children")) {
			  a.children.some(b => {
				    if (b.name === r_name) {
					      targetRouter = b;
					      exit = true;
					      return true
				    }
			  });
		}
		
		if (exit) return true;
	})
	// 后清除缓存,先清除的话会刷新当前关闭标签
	setTimeout(() => {
		  if (targetRouter) {
			    targetRouter.meta.closed = true;
			    const onlyKey = 随机值;
		      	target.meta.timeKey = onlyKey;
		      	this.$store.commit('setCacheNameRemovePending', targetRouter.name);
		  }
	});
复制标签路由
/**
* 复制的逻辑相当于手动拼接一个带有_redirect_[name]的路由链接出来
* 访问这个链接的话就可以直接走beforeRouter的第二个if了,后续会自动生成路由
*/
copyPath(){
	let route = this.$route;
	let { fullPath, name } = route;
	// 重定向路由
	let newUrl = '';
	let onlyKey = 随机值;
	if (fullPath.indexOf('_redirect_') > -1) {
		newUrl = `${fullPath.slice(0, fullPath.lastIndexOf("/"))}/${onlyKey}`;
	} else {
		newUrl = `${fullPath}/_redirect_${name}/${onlyKey}`;
	}
	
	if (newUrl) location.href = `#${newUrl}`;
}
几个坑要注意
  • keepAlive对于多级路由的兼容性不好,所以不要超过2级或者统一转换成一级
  • 新生成router的components需要指向已存在路由
  • router新增后options.routes不会自动添加,需要手动加进去
  • 偶尔改变timeKey后,router-view不会自动刷新,可以通过在router-view加个v-if来解决
  • 新生成的路由的话没法通过更改includes和excludes来销毁缓存,$destroy可以销毁,但要注意需要把dom内存同样销毁掉,要不会爆栈的
参考
  • 源码,vue(v2.6.11)相关的keep-alive源码位置在src/core/components/keep-alive.js
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值