vue缓存页面怎样管理?什么时候销毁及如何销毁?
Admin 2021-10-25 群英技术资讯 1847 次浏览
今天我们来了解的是vue缓存页面怎样管理的内容,对于vue缓存页面的管理的问题的就是什么时候销毁及如何销毁,那么具体怎样做呢?
下面跟随小编一起来了解看看吧。
<keep-alive> <router-view /> </keep-alive>
Vue中内置的<keep-alive>组件可以帮助我们在开发SPA应用时,通过把全部路由页面进行缓存(当然也可以有针对性的缓存部分页面),显著提高页面二次访问速度,但是也给我们在某些场景带来了困扰,其中包含两个主要矛盾:
本文主要围绕这两个问题探讨,后文用问题一和问题二指代。本文默认所有页面都是keep-alive
当随着业务逻辑变得复杂,路由栈也逐渐升高,理论上用户可以无限的路由下去,不可避免的我们需要管理这些缓存在内存中的页面数据,页面数据包含两部分,Vue实例和对应的Vnode。查看 Vue 源码中src/core/components/keep-alive.js关于缓存的定义
this.cache = Object.create(null) //用来缓存vnode cache[key] => Vnode this.keys = [] //用来记录已缓存的vnode的key
缓存后并不会重用 Vnode,而是只用它上面挂载的 Vue 实例。
if (cache[key]) { vnode.componentInstance = cache[key].componentInstance //仅从缓存的vnode中获取vue实例挂在到新的vnode上 // make current key freshest remove(keys, key) keys.push(key) }
为什么不用呢,因为有BUG,最早一版实现里确实是会直接使用缓存的 Vnode。
出自src/core/components/keep-alive.js init version
export default { created () { this.cache = Object.create(null) }, render () { const childNode = this.$slots.default[0] const cid = childNode.componentOptions.Ctor.cid if (this.cache[cid]) { const child = childNode.child = this.cache[cid].child //直接获取缓存的vnode childNode.elm = this.$el = child.$el } else { this.cache[cid] = childNode } childNode.data.keepAlive = true return childNode }, beforeDestroy () { for (const key in this.cache) { this.cache[key].child.$destroy() } } }
我们需要管理的其实就是cache和keys,keep-alive提供了三个参数来动态管理缓存:
include - 只有名称匹配的组件会被缓存。 exclude - 任何名称匹配的组件都不会被缓存。 max - 最多可以缓存多少组件实例。
它们的作用非常简单,源码写的也很简单易读:
所以当我们想要管理这些缓存时,简单的方案就是操作这三个参数,修改include和exclude来缓存或者清除某些缓存,但是需要注意的是它们匹配的是组件的name:
出自src/core/components/keep-alive.js
const name: ?string = getComponentName(componentOptions)
所以清除缓存是会无差别的把某个组件的所有实例全部清除,这显然不满足我们的需求。
max的逻辑则是超过最大值时清除栈底的缓存,
出自src/core/components/keep-alive.js:
if (this.max && keys.length > parseInt(this.max)) { pruneCacheEntry(cache, keys[0], keys, this._vnode) }
我们要解决问题一,官方提供给的API走不通,我们只能自己来了,我们需要的是解决两个子问题:
先看怎么销毁,如果想销毁一个实例很简单,可以直接用 this.$destroy(), 这样可以吗,不行,这样缓存cache和keys中依旧保留了原来的vnode和key,再次访问时就会出现问题,vnode一直被留存,但是它身上的实例已经被销毁了,这时候在vue的update过程中就会再去创建一个vue实例,也就是说只要某个keep-alive的页面调用过一次this.$destroy(),但是没有清理缓存数组,这个页面之后被重新渲染时就一定会重新创建一个实例,当然重新走全部的生命周期。现象最终就是这个页面就像是没有被缓存一样。
this.$destroy(); //不适合keep-alive组件
所以销毁需要同时清理掉缓存cache和keys,下面定义了一个同时清除缓存的$keepAliveDestroy方法:
const dtmp = Vue.prototype.$destroy; const f = function() { 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 = !isDef(this.$vnode.key) ? 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]; } } } } dtmp.apply(this, arguments); } Vue.prototype.$keepAliveDestroy = f;
那么什么时候销毁呢,有两个触发时机:
replace 比较简单,我们可以直接拦截router的replace方法,在该方法中清除掉当前页面。(这里也有例外,比如切换Tab时,最后再说)
我们具体来看看route back这种情况,如果说我们的页面上有一个返回键,那么在这里清除缓存是非常正确的时机,但是我们不能忽略浏览器自带的返回键和安卓机上的物理返回键,这种情况考虑进来以后,仅使用返回键的方案就不能满足了。
每次push或者replace是都增加query上一个参数,来记录当前深度
this.$router.push({ path:"/targer", query:{ stackLevel:Number(this.$route.query.stackLevel) + 1 } })
这个方案有明显弊端,外部暴露一个参数是非常丑陋且危险的,用户可以随便修改,在进行网页推广时,业务去生产环境自己拷贝到的推广链接也可能带着一个奇怪的 https://xxx.com/foo?bar=123&stackLevel=13后缀。弃用
hack掉router的push和replace方法以后,每次跳转的时候都可以给目标页的vm挂载一个_stackLevel,这样就解决了方案一的问题,不暴露给用户,URL中不可见,也无法修改,但是我们不能忽视浏览器中另一个恶魔――刷新键,在刷新的时候URL不会变,但是vm实例就需要重新创建了,那么我们的栈深度标示也就丢失了。弃用
那么最终就是既可以对用户不可见,又可以在刷新的时候得以保存。那就是history.state了,所以我们需要做的就是把stack深度保存到history.state中,它能够完整的保存整个路由链条。
当我们获取到目标页面栈深度小于当前页面时,我们就可以销毁当前页面了。
if(target.stack < current.stack){ current.$keepAliveDestroy(); }
可以在源码中看到 src/core/components/keep-alive.js
const key: ?string = vnode.key == null // same constructor may get registered as different local components // so cid alone is not enough (#3269) ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') : vnode.key 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如果没有key才会使用组件名,所以默认缓存中的key是组件名,如果组件相同时,我们在每个页面都有自己的key就可以解决这个问题了,如何实现每个页面拥有自己的key呢。有两个子问题:
key = Date.now()
key = vm._stack + router.currentRoute.path 这个方案利用当前的栈高度+路径名,为什么需要路径名呢,因为replace的时候栈高度不变,只是路径名变了。
目前有两个方案给vue-router当前的Vnode的key来赋值:
这个方案实现比较简单
//绑定key ... <router-view :key='$route.query.routerKey' /> ... //push时 this.$router.push({ path:"/foo", query:{ routerKey: Date.now() //随机key } })
这种方式用起来非常简单有效,但是缺点同样也是会暴露一个奇怪的参数在URL中
在哪个阶段给Vnode的key赋值呢,答案显而易见,在keep-alive组件render函数进入前, src/core/components/keep-alive.js
... render () { const slot = this.$slots.default const vnode: VNode = getFirstComponentChild(slot) ...
我们可以hack掉keep-alive的render函数,然后在这之前先把slot里的第一个子节点拿到以后,给它的key进行赋值,然后再调用 keep-alive的render:
const tmp = vm.$options.render //vm is keep-alive component instance vm.$options.render = function() { const slot = this.$slots.default; const vnode = getFirstComponentChild(slot) // vnode is a keep-alive-component-vnode if (historyShouldChange) { if (!isDef(vnode.key)) { if (isReplace) { vnode.key = genKey(router._stack) } else if (isPush()) { vnode.key = genKey(Number(router._stack) + 1) } else { vnode.key = genKey(Number(router._stack) - 1) } } } else { // when historyShouldChange is false should rerender only, should not create new vm ,use the same vnode.key issue#7 vnode.key = genKey(router._stack) } return tmp.apply(this, arguments) }
关于"vue缓存页面怎样管理"的内容就介绍到这,上述实例具有一定的借鉴价值,感兴趣的朋友可以参考,希望能对大家有帮助,想要了解更多vue缓存页面的内容,大家可以关注其它的相关文章。
文本转载自脚本之家
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
怎样用js实现图片隐藏和显示效果?图片的隐藏和显示效果我们经常能在网站上看到,这个功能也是比较实用的,很多朋友想要知道如何用JavaScript来实现图片隐藏和显示,下面小编就给大家分享一些js实现图片隐藏和显示的代码供大家参考。
本文给大家分享的是React项目中出现事件节流效果失效的解决方法,本文会介绍React事件节流失效的原因及两种解决方式,有需要的朋友可以参考了解看看,下面我们就来看一下示例吧。
javascript键值对中的key是可以为变量的,比如js json对象定义的时候也有可能key就是变量的,我们就可以这样做。 js代码如下。 varuserJson=[];//假如userId就是一个js变量varuserId=getUserId();varstr={+userId+:china};//将字符串转化为js对象varobj=eval
本文实例讲述了JavaScript闭包原理与用法。分享给大家供大家参考,具体如下:
这篇文章主要为大家详细介绍了JavaScript点击按钮生成4位随机验证码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008