vue单页缓存方案分析及实现


Posted in Javascript onSeptember 25, 2018

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

方案一:vue的keep-alive组件

 具体使用如下: 

<keep-alive max="10">
  <router-view/>
 </keep-alive>

为什么这么使用?

如vue官网(https://cn.vuejs.org/v2/api/#keep-alive)介绍:

<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> 中建立缓存,和普通组件渲染没什么区别。从进入到返回的大致执行流程如下

vue单页缓存方案分析及实现

能实现的功能

能够把要缓存的组件渲染的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,所以使用单页需要注意全局变量或是事件的谨慎使用,具体的踩坑记录在https://3water.com/article/147957.htm

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
DOM下的节点属性和操作小结
May 14 Javascript
jQuery文本框(input textare)事件绑定方法教程
Apr 24 Javascript
javascript学习笔记(八)正则表达式
Oct 08 Javascript
jQuery中prev()方法用法实例
Jan 08 Javascript
javascript动态设置样式style实例分析
May 13 Javascript
AngularJS实现DOM元素的显示与隐藏功能
Nov 22 Javascript
jQuery实现的鼠标响应缓冲动画效果示例
Feb 13 jQuery
AngularJS实现动态切换样式的方法分析
Jun 26 Javascript
vue中利用Promise封装jsonp并调取数据
Jun 18 Javascript
原生js基于canvas实现一个简单的前端截图工具代码实例
Sep 10 Javascript
JSONP 的原理、理解 与 实例分析
May 16 Javascript
解决vue2中使用elementUi打包报错的问题
Sep 22 Javascript
Vue中对拿到的数据进行A-Z排序的实例
Sep 25 #Javascript
vue单页缓存存在的问题及解决方案(小结)
Sep 25 #Javascript
深入理解与使用keep-alive(配合router-view缓存整个路由页面)
Sep 25 #Javascript
使用vue2.0创建的项目的步骤方法
Sep 25 #Javascript
vue解决一个方法同时发送多个请求的问题
Sep 25 #Javascript
Vue2.0学习系列之项目上线的方法步骤(图文)
Sep 25 #Javascript
在vue中多次调用同一个定义全局变量的实例
Sep 25 #Javascript
You might like
jQuery中的RadioButton,input,CheckBox取值赋值实现代码
2014/02/18 PHP
试用php中oci8扩展
2015/06/18 PHP
PHP将二维数组某一个字段相同的数组合并起来的方法
2016/02/26 PHP
Javascript 汉字字节判断
2009/08/01 Javascript
ANGULARJS中使用JQUERY分页控件
2015/09/16 Javascript
详解jQuery Mobile自定义标签
2016/01/06 Javascript
jQuery绑定事件on()与弹窗的简要概述
2016/04/27 Javascript
JS HTML5实现拖拽移动列表效果
2020/08/27 Javascript
AngularJS入门教程二:在路由中传递参数的方法分析
2017/05/27 Javascript
使用Require.js封装原生js轮播图的实现代码
2017/06/15 Javascript
详解angularjs获取元素以及angular.element()用法
2017/07/25 Javascript
利用JS测试目标网站的打开响应速度
2017/12/01 Javascript
微信小程序 scroll-view实现锚点滑动的示例
2017/12/06 Javascript
浅谈微信JS-SDK 微信分享接口开发(介绍版)
2018/08/15 Javascript
微信小程序文章详情页跳转案例详解
2019/07/09 Javascript
Javascript如何实现双指控制图片功能
2020/02/25 Javascript
vue-quill-editor的使用及个性化定制操作
2020/08/04 Javascript
[43:49]LGD vs CHAOS 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/16 DOTA
快速了解Python相对导入
2018/01/12 Python
python版本单链表实现代码
2018/09/28 Python
Python 离线工作环境搭建的方法步骤
2019/07/29 Python
Python 中 -m 的典型用法、原理解析与发展演变
2019/11/11 Python
python3发送request请求及查看返回结果实例
2020/04/30 Python
Keras之fit_generator与train_on_batch用法
2020/06/17 Python
CSS3实现的文本3D效果附图
2014/09/03 HTML / CSS
美国购买体育赛事门票网站:TicketCity
2019/03/06 全球购物
委托与事件是什么关系?为什么要使用委托
2014/04/18 面试题
前台文员个人求职信范文
2014/01/05 职场文书
物理专业本科生自荐信
2014/01/30 职场文书
党委书记个人对照检查材料
2014/09/15 职场文书
完整版商业计划书
2014/09/15 职场文书
高一军训决心书
2015/02/05 职场文书
谢师宴家长致辞
2015/07/27 职场文书
2015年七夕情人节感言
2015/08/03 职场文书
优秀团员主要事迹材料
2015/11/05 职场文书
Python内置数据结构列表与元组示例详解
2021/08/04 Python