首页 > 语言 > JavaScript > 正文

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

2024-05-06 15:40:42
字体:
来源:转载
供稿:网友

feb-alive

github地址
体验链接

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

在剖析feb-alive实现之前,希望大家对以下基本知识有一定的了解。

keep-alive实现原理 history api vue渲染原理 vue虚拟dom原理

feb-alive与keep-alive差异性

1. 针对activated钩子差异性

keep-alive配合vue-router在动态路由切换的情况下不会触发activated钩子,因为切换的时候组件没有变化,所以只能通过beforeRouteUpdate钩子或者监听$route来实现数据更新,而feb-alive在动态路由切换时,依然会触发activated钩子,所以用户可以放心的将业务更新逻辑写在activated钩子,不必关心动态路由还是非动态路由的情况。

2. feb-alive是页面级缓存,而keep-alive是组件级别缓存

所以在上文中讲到的使用keep-alive存在的一些限制问题都能够得到有效的解决

实现原理

首先我们的目标很明确,需要开发的是一个页面级别的缓存插件,之前使用keep-alive遇到的诸多问题,归根结底是因为它是一个组件级别的缓存。那么我们就需要寻找每个页面的特征,用来存储我们需要存储的路由组件vnode,这里我们就需要思考什么可以作为每个页面的标记

两种方式:

通过每个url的查询参数来存储key 通过history.state来存储key

方案一:使用查询参数

优点:

可以兼容vue-router的hash模式

缺点:

每个页面的url后面都会带一个查询参数
每次页面跳转都需要重写url

方案二:使用history.state

优点:

无需附带额外的查询参数

缺点:

不支持hash模式

相比方案一明显的缺点,我更较倾向于方案二,舍弃hash模式的兼容性,换来整个插件更加好的用户体验效果。
接下来看下feb-alive的实现,feb-alive组件与上文的keep-alive一样都是抽象组件,结构基本一致,主要区别在于render函数的

实现

// feb-alive/src/components/feb-alive.jsrender () {  // 取到router-view的vnode  const vnode = this.$slots.default ? this.$slots.default[0] : null  const disableCache = this.$route.meta.disableCache  // 如果不支持html5 history则不做缓存处理  if (!supportHistoryState) {    return vnode  }  // 尝试写入key  if (!history.state || !history.state[keyName]) {    const state = {      [keyName]: genKey()    }    const path = getLocation()    history.replaceState(state, null, path)  }  // 有些浏览器不支持往state中写入数据  if (!history.state) {    return vnode  }  // 指定不使用缓存  if (disableCache) {    return vnode  }  // 核心逻辑  if (vnode) {    const { cache, keys } = this    const key = history.state[keyName]    const { from, to } = this.$router.febRecord    let parent = this.$parent    let depth = 0    let cacheVnode = Object.create(null)    vnode && (vnode.data.febAlive = true)    while (parent && parent._routerRoot !== parent) {      if (parent.$vnode && parent.$vnode.data.febAlive) {        depth++      }      parent = parent.$parent    }    // 记录缓存及其所在层级    febCache[depth] = cache    // /home/a backTo /other    // 内层feb-alive实例会被保存,防止从/home/a 跳转到 /other的时候内层feb-alive执行render时候,多生成一个实例    if (to.matched.length < depth + 1) {      return null    }    if (from.matched[depth] === to.matched[depth] && (from.matched.slice(-1)[0] !== to.matched.slice(-1)[0])) {      // 嵌套路由跳转 && 父级路由      // /home/a --> /home/b      // 父路由通过key进行复用      cache[key] = cache[key] || this.keys[this.keys.length - 1]      cacheVnode = getCacheVnode(cache, cache[key])      if (cacheVnode) {        vnode.key = cacheVnode.key        remove(keys, key)        keys.push(key)      } else {        this.cacheClear()        cache[key] = vnode        keys.push(key)      }    } else {      // 嵌套路由跳转 && 子路由      // 正常跳转 && 动态路由跳转      // /a --> /b      // /page/1 --> /page/2      vnode.key = `__febAlive-${key}-${vnode.tag}`      cacheVnode = getCacheVnode(cache, key)      // 只有相同的vnode才允许复用组件实例,否则虽然实例复用了,但是在patch的最后阶段,会将复用的dom删除      if (cacheVnode && vnode.tag === cacheVnode.tag) {        // 从普通路由后退到嵌套路由时,才需要复原key        vnode.key = cacheVnode.key        vnode.componentInstance = cacheVnode.componentInstance        remove(keys, key)        keys.push(key)      } else {        this.cacheClear()        cache[key] = vnode        keys.push(key)      }    }    vnode.data.keepAlive = true  }  return vnode}            
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表

图片精选