博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
vue如何实现单页缓存方案分析
阅读量:6716 次
发布时间:2019-06-25

本文共 6467 字,大约阅读时间需要 21 分钟。

实现全站的页面缓存,前进刷新,返回走缓存,并且能记住上一页的滚动位置,参考了很多技术实现,github上的导航组件实现的原理要么使用的keep-alive,要么参考了keep-alive的源码,但是只用keep-alive没法实现相同path,不同参数展示不同view,这就有点坑了,所以需要结合自己要实现的功能,适当改造keep-alive,为了实现每次前进都能刷新,返回走缓存还能自动定位的功能,文章陆续从以下几个方面展开讲:两套技术方案可选,最后定的技术方案的原因,实现的功能和原理,踩过的坑

方案一:vue的keep-alive组件

 

具体使用如下: 

为什么这么使用?

如vue官网()介绍:

<keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 <transition> 相似,<keep-alive> 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。

当组件在 <keep-alive> 内被切换,它的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行。主要用于保留组件状态或避免重新渲染。

因为缓存的需要通常出现在切换页面时,所以就需要结合vue-router的router-view来实现

为什么keep-alive能实现缓存?

 

render () {    const slot = this.$slots.default    const vnode: VNode = getFirstComponentChild(slot)    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions    if (componentOptions) {      // check pattern      const name: ?string = getComponentName(componentOptions)      const { include, exclude } = this      if (        // not included        (include && (!name || !matches(include, name))) ||        // excluded        (exclude && name && matches(exclude, name))      ) {        return vnode      }      const { cache, keys } = this      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.data.keepAlive = true    }    return vnode || (slot && slot[0])  }

 

如上keep-alive源码,其中render函数是这样实现的,要渲染的试图组件作为插槽内容被获取到,当渲染到路径匹配到的视图组件时会根据vnode存储的内容拿到对应的name,一次将这些组件实例放到变量cache中,这样根据路由就可以找到缓存的vnode,返回给createComponent方法去执行initComponent,vue组件渲染这块的代码如下

function initComponent (vnode, insertedVnodeQueue) {  if (isDef(vnode.data.pendingInsert)) {    insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)    vnode.data.pendingInsert = null  }  vnode.elm = vnode.componentInstance.$el  if (isPatchable(vnode)) {    invokeCreateHooks(vnode, insertedVnodeQueue)    setScope(vnode)  } else {    // empty component root.    // skip all element-related modules except for ref (#3455)    registerRef(vnode)    // make sure to invoke the insert hook    insertedVnodeQueue.push(vnode)  }}

 

这里会有 vnode.elm 缓存了 vnode 创建生成的 DOM 节点。所以对于首次渲染而言,除了在 <keep-alive> 中建立缓存,和普通组件渲染没什么区别。从进入到返回的大致执行流程如下

前进-返回

能实现的功能

能够把要缓存的组件渲染的vnode记到cache里边,当返回的时候用缓存里边的dom直接渲染,还有keep-alive组件提供的include 和 exclude属性,可以有条件的缓存想缓存的组件,如果配置了 max 并且缓存的长度超过了这个max的值,还要从缓存中删除第一个

存在的问题

存在的问题是存储vnode节点的key是name,也就是定义路由时组件对应的name,这就会导致同样的path,不同参数的时候打开的是从cache里边拿到的vnode,会渲染出同样的视图出来,但是很多业务场景都是根据参数来显示不同内容,而keep-alive底层并没有对此做扩展,可以看下keep-alive源码

const key: ?string = vnode.key == null        ? 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就是路由里边定义的name,所以要用这套方案来实现的根据不同参数展示不同视图的功能就要对这里的key做改造,但是keep-alive是vue自带的,没法改底层,然后就诞生了我的第二套方案

 

方案二:navigation组件,scrollbehavior 

github上找到类似功能的组件vue-navigation,这个vue组件可以实现返回走缓存,底层原理跟keep-alive一样,实际上是改写了keep-alive组件,前进刷新时新增了一个参数VNK,这样在路由发生变化的时候都会用给url带一个参数,并且cache的key取值依赖这个参数,借鉴这个组件的思路,做了一个类似keep-alive的组件,其中key的值是getKey方法获取的,改写以后的render方法如下

render () {      var vnode = this.$slots.default ? this.$slots.default[0] : null      if (vnode) {        vnode.key = vnode.key || (vnode.isComment ? 'comment' : vnode.tag)        const { cache, keys } = this        var key = getKey(this.$route, keyName)        if (vnode.key.indexOf(key) === -1) {          vnode.key = '__navigation-' + key + '-' + vnode.key        }        if (cache[key]) {          if (vnode.key === cache[key].key) {            vnode.componentInstance = cache[key].componentInstance          } else {            cache[key].componentInstance.$destroy()            cache[key] = vnode          }          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    }

 getKey方法实现

//url上新增参数vnk的值

export function genKey() {  // const t  = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'  const t = 'xxxxxxxx'  return t.replace(/[xy]/g, function (c) {    const r = Math.random() * 16 | 0    const v = c === 'x' ? r : (r & 0x3 | 0x8)    return v.toString(16)  })}//export function getKey(route, keyName) {  return `${route.name || route.path}?${route.query[keyName]}`} 

通过新写一个install方法挂载这个导航组件到vue上就可以实现前进刷新,返回走缓存,并且可以配置最大缓存数,后续开源到github

最后剩下返回上一页记住上一页的位置,之所以没有用开源的这个组件的记位置,是因为直接套用需要改整体布局,height:100%;样式造成$(windows).scrollTop失效,整体考虑改造成本较大,还是使用了vue-router提供的scrollBehavior,在路由配置里引入

实现如下:

var scrollBehavior = async (to, from, savedPosition) => {  if (savedPosition) {    return savedPosition  } else {    return new Promise((resolve, reject) => {      setTimeout(() => {        resolve({ x: 0, y: to.meta.savedPosition || 0 })      }, 300)    })  }}const router = new VueRouter({  mode: 'history',  scrollBehavior,  routes: [{    path: '',    redirect: '/mobile/home.html',    meta: {      needMtaReport: true,      parentsStyle: {        height: '100%',        minHeight: '100%'      }    }  },  {    name: 'scienceCompetition',    path: '/mobile/scienceCompetition.html',    component: scienceCompetition  }]}

总结:

1.单页缓存下js加载解析编译执行的时间缩短了,返回的时候由于走缓存js脚本的占用时间完全可以忽略,从而整体上缩减了页面的加载渲染时间

  2. 因为项目以前不是单页,代码里边定义了很多全局变量或者全局事件绑定,改成单页后全局变量的值依然存在,就会导致业务逻辑出现bug,所以使用单页需要注意全局变量或是事件的谨慎使用,具体的踩坑记录在

  3.通过push进入下一页时,head里边会累加前面页面的静态资源,访问的页面越多,最后的页面挂的静态的资源越多,返回的时候并不会减少已经加载的静态资源,单页缓存是典型的空间换时间的方案,内存的开销比较大,能否对资源动态增减以及内存占用的优化一直在探索中,暂时没有找到很好的解决方法。。。。。

转载地址:http://osumo.baihongyu.com/

你可能感兴趣的文章
Facebook抄袭Snap真的十恶不赦吗?
查看>>
AUTO_INCREMENT列在InnoDB里如何工作
查看>>
外勤365:提升企业线下销售效率,成就移动外勤首选品牌
查看>>
防范网络犯罪组合拳 警企合作是关键
查看>>
黑客入侵好莱坞越发失控 黑客组织高调发声明威胁
查看>>
结合重复数据删除和Rowhammer的攻击怎么解?
查看>>
携手美国糖尿病协会,IBM将用大数据改变糖尿病的未来
查看>>
再续 asp.net 域名欺骗式开发之泛解析域名
查看>>
先是山寨后是烂大街 联发科的症结在哪?
查看>>
Snapchat升级聊天功能:增加贴纸、语音和视频
查看>>
5000万美元融资构建产业链!EasyStack深耕开源云
查看>>
因为你没加密 所以网络保险不给你理赔
查看>>
iOS零日漏洞赏金已升至150万美元
查看>>
印媒:中国厂商势头强劲 索尼三星在印度裁员
查看>>
苹果不再披露广告支出 或为掩饰销售额背后的支出增长
查看>>
中国太阳能续增 补贴、技术转型均受重视
查看>>
北京信息化协会人工智能专委会成立 AI产业快速腾飞
查看>>
《PostgreSQL服务器编程》一一1.9 关于缓存
查看>>
IBM芯片计划:生物是设计更高效芯片的关键
查看>>
没有这个黑客,就不会有你现在用的开源软件
查看>>