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

news/2024/7/10 0:30:14 标签: vue

实现全站的页面缓存,前进刷新,返回走缓存,并且能记住上一页的滚动位置,参考了很多技术实现,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)介绍:

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

当组件在 内被切换,它的 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 节点。所以对于首次渲染而言,除了在 中建立缓存,和普通组件渲染没什么区别。从进入到返回的大致执行流程如下

能实现的功能

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

存在的问题

存在的问题是存储vnode节点的key是name,也就是定义路由时组件对应的name,这就会导致同样的path,不同参数的时候打开的是从cache里边拿到的vnode,会渲染出同样的视图出来,但是很多业务场景都是根据参数来显示不同内容,而keep-alive底层并没有对此做扩展,可以看下keep-alive源码
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里边会累加前面页面的静态资源,访问的页面越多,最后的页面挂的静态的资源越多,返回的时候并不会减少已经加载的静态资源,单页缓存是典型的空间换时间的方案,内存的开销比较大,能否对资源动态增减以及内存占用的优化一直在探索中,暂时没有找到很好的解决方法。。。

最后

为了帮助大家让学习变得轻松、高效,给大家免费分享一大批资料,帮助大家在成为全栈工程师,乃至架构师的路上披荆斩棘。在这里给大家推荐一个前端全栈学习交流圈:866109386.欢迎大家进群交流讨论,学习交流,共同进步。

当真正开始学习的时候难免不知道从哪入手,导致效率低下影响继续学习的信心。

但最重要的是不知道哪些技术需要重点掌握,学习时频繁踩坑,最终浪费大量时间,所以有有效资源还是很有必要的。

最后祝福所有遇到瓶疾且不知道怎么办的前端程序员们,祝福大家在往后的工作与面试中一切顺利。


http://www.niftyadmin.cn/n/1300807.html

相关文章

vue中各种通信传值方式总结

1、路由通信传值 路由通信是通过路由跳转用query把参数带过去&#xff0c;也是vue常用的通信手段。 例子&#xff1a;创建并在路由注册一个组件Head <template><div id"head"><button click"handleChange">clickMe</button> //…

volatile关键字总结

volatile的本意是“易变的” 由于访问寄存器的速度要快过RAM&#xff0c;所以编译器一般都会作减少存取外部RAM的优化。如果没有volatile关键字&#xff0c;则编译器可能优化读取和存储&#xff0c;可能暂时使用寄存器中的值&#xff0c;如果这个变量由别的程序更新了的话&…

vuex实现及简略解析(小结)

大家都知道vuex是vue的一个状态管理器&#xff0c;它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。先看看vuex下面的工作流程图 通过官方文档提供的流程图我们知道&#xff0c;vuex的工作流程&#xff0c; 1、数据…

从0到1构建vueSSR项目之路由的构建

vue开发依赖的相关配置 Vue SSR 指南 今天先做客户端方面的配置&#xff0c;明天再做服务端的部分。 那么马上开始吧~ 修改部分代码 脚手架生成的代码肯定是不适合我们所用的 所以要修改一部分代码 //App.vue <template><div id"app"><router-v…

CONTAINING_RECORD 宏

CONTAINING_RECORD Containing record是一个在C编程中用处很大的一种技巧&#xff0c;它的功能为已知结构体或类的某一成员、对象中该成员的地址以及这一结构体名或类名&#xff0c;从而得到该对象的基地址。 由于写法简单&#xff0c;它被当做一个宏来使用&#xff0c;写法是这…

assert函数 与 断言宏(ASSERT)

assert函数 assert.h是c标准库的一个头文件,该头文件的主要目的就是提供一个assert的宏定义。 assert只是对所给的表达式求值,就像if判断语句中一样,然后如果该值为真则正常运行,否则报错,并调用abort(),产生异常中断,exit出来。

C++ 结构体中数组可以直接赋值

C语言中, 数组定义时如果没有赋值, 之后是不可以使用=赋值的,需要逐一成员赋值或者用memcpy函数。 但是在C++中结构体中的数组可以(无需重载=运算符),需要区分以下3种情况: #include<iostream> #include<string> using namespace std; struct A { c…

C语言进阶—C语言高手之路视频教程-黄强-专题视频课程

C语言进阶—C语言高手之路视频教程—333人已学习 课程介绍 C语言高手之路&#xff0c;大量C语言干货让你分分钟从小白变大牛 &#xff01; 本视频中包含的知识点&#xff1a; 1、标准教程中很少出现&#xff0c;却非常重要的知识点。 2、企业面试绝对会考察的知识点&#xf…