浅谈Vue页面级缓存解决方案feb-alive(上)

github地址
体验链接

使用理由

开发者无需因为动态路由或者普通路由的差异而将数据初始化逻辑写在不同的钩子里beforeRouteUpdate或者activated

开发者无需手动缓存页面状态,例如通过localStorage或者sessionStorage缓存当前页面的数据

feb-alive会帮你处理路由meta信息的存储与恢复

为什么开发feb-laive?

当我们通过Vue开发项目时候,是否会有以下场景需求?

/a跳转到/b

后退到/a时候,希望从缓存中恢复页面

再次跳转到/b时,分两种情况

情况一: 通过链接或者push跳转,则希望重新创建/b页面,而不是从缓存中读取

情况二: 如果点击浏览器自带前进按钮,则还是从缓存中读取页面。

这个场景需求着重强调了缓存,缓存带来的好处是,我上次页面的数据及状态都被保留,无需在从服务器拉取数据,使用户体验大大提高。

尝试用keep-alive实现页面缓存

<keep-alive> <router-view></router-view> </keep-alive>

so easy但是理想很完美,现实很残酷

存在问题

-/a跳到/b,再跳转到/a 的时候,页面中的数据是第一次访问的/a页面,明明是链接跳转,确出现了缓存的效果,而我们期望的是像app一样开启一个新的页面。

同理动态路由跳转/page/1->/page/2因为两个页面引用的是同一个组件,所以跳转时页面就不会有任何改变,因为keep-alive的缓存的key是根据组件来生成的(当然Vue提供了beforeRouteUpdate钩子供我们刷新数据)

总结:keep-alive的缓存是==组件级别==的,而不是==页面级别==的。

举个应用场景

例如浏览文章页面,依次访问3篇文章

/artical/1

/artical/2

/artical/3

当我从/artical/3后退到/artical/2时候,由于组件缓存,此时页面还是文章3的内容,所以必须通过beforeRouteUpdate来重新拉取页面2的数据。(注意此处后退不会触发组件的activated钩子,因为两个路由都渲染同个组件,所以实例会被复用,不会执行reactivateComponent)

如果你想从/artical/3后退到/artical/2时,同时想恢复之前在/artical/2中的一些状态,那么你还需要自己针对/artical/2中的所有状态数据进行存储和恢复。

综上:keep-alive实现的组件级别的缓存和我们想象中的缓存还是有差距的,keep-alive并不能满足我们的需求。

==针对这些问题,所以feb-alive插件诞生了==

由于feb-alive是基于keep-alive实现的,所以我们先简单分析一下keep-alive是如何实现缓存的

export default { name: 'keep-alive', abstract: true, props: { include: patternTypes, exclude: patternTypes, max: [String, Number] }, created () { this.cache = Object.create(null) this.keys = [] }, destroyed () { for (const key in this.cache) { pruneCacheEntry(this.cache, key, this.keys) } }, mounted () { this.$watch('include', val => { pruneCache(this, name => matches(val, name)) }) this.$watch('exclude', val => { pruneCache(this, name => !matches(val, name)) }) }, render () { // 获取默认插槽 const slot = this.$slots.default // 获取第一个组件,也就和官方说明的一样,keep-alive要求同时只有一个子元素被渲染,如果你在其中有 v-for 则不会工作。 const vnode: VNode = getFirstComponentChild(slot) // 判断是否存在组件选项,也就是说只对组件有效,对于普通的元素则直接返回对应的vnode const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions if (componentOptions) { // 检测include和exclude 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 // 如果指定了子组件的key则使用,否则通过cid+tag生成一个key const key: ?string = vnode.key == null ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') : vnode.key // 判断是否存在缓存 if (cache[key]) { // 直接复用组件实例,并更新key的位置 vnode.componentInstance = cache[key].componentInstance remove(keys, key) keys.push(key) } else { // 此处存储的vnode还没有实例,在之后的流程中通过在createComponent中会生成实例 cache[key] = vnode keys.push(key) // 当缓存数量大于阈值时,删除最早的key if (this.max && keys.length > parseInt(this.max)) { pruneCacheEntry(cache, keys[0], keys, this._vnode) } } // 设置keepAlive属性,createComponent中会判断是否已经生成组件实例,如果是且keepAlive为true则会触发actived钩子。 vnode.data.keepAlive = true } return vnode || (slot && slot[0]) } }

keep-alive是一个抽象组件,组件实例中维护了一份cache,也就是以下代码部分

created () { // 存储组件缓存 this.cache = Object.create(null) this.keys = [] }

由于路由切换并不会销毁keep-alive组件,所以缓存是一直存在的(嵌套路由中,子路由外层的keep-alive情况会不一样,后续会提到)

继续看下keep-alive在缓存的存储和读取的具体实现,先用一个简单的demo来描述keep-alive对于组件的缓存以及恢复缓存的过程

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。