Vue 有条件路由缓存,就像传统新闻网站一样

114 阅读4分钟

前言

这是我的 模仿抖音 系列文章的第四篇:这篇我们将实现有条件的路由缓存,就像我们访问传统新闻网站一样

第一篇:200行代码实现类似Swiper.js的轮播组件
第二篇:实现抖音 “视频无限滑动“效果
第三篇:Vue 路由使用介绍以及添加转场动画
第五篇:Github Actions 部署 Pages、同步到 Gitee、翻译 README 、 打包 docker 镜像

最终效果

在线预览:dy.ttentau.top/
因为这个项目是为移动端做的,推荐大家切换到手机模式访问。先按F12调出控制台,再按Ctrl+Shift+M切换到手机模式

Github地址:github.com/zyronon/dou…

实现

原理

KeepAlive 组件介绍:cn.vuejs.org/guide/built…

Vue 内置的 <KeepAlive> 组件可以缓存任何组件实例,只需要像下面这样写就可以缓存所有组件了

<router-view v-slot="{ Component }">
  <keep-alive>
    <component :is="Component" />
  </keep-alive>
</router-view>

问题

可是,这样写的话,那么所有的组件都被无差别缓存了,这显然不是我们希望看到的效果。

我们期望的效果是什么?就像 jQuery 时代的网站(例如:www.v2ex.com )一样,点击一个链接,前进到下一页,浏览完成之后,返回上一页,当前页面被丢弃。再次访问的话需要重新加载

吐槽:找了一圈国内的知名网站,点击链接全是单独打开一个标签页,根本没有在当前页跳转的了!

有喜欢逛 v2ex 的朋友可以试试我开发的这个油猴脚本:V2Next ,有UI美化、楼中楼、回复上下文、高赞回复、简洁模式、发送图片和表情 emoji、base64 解码等功能,现在每天都有约1800个 V 友在使用!

大多数情况下,用户进入一个页面,这个页面会请求数据。用户浏览完毕退出页面。如果再次进入,页面应该像重新打开了一样,开始请求数据,这是符合用户预期的
而不是不管重新进入多少次,页面都是一开始进入的样子,这时用户可能会怀疑自己是否断网了?我们总不能在每个页面都写 onActivatedonDeactivated 方法去处理数据请求的逻辑吧?

解决

KeepAlive 的 exclude 属性

exclude 属性可以排除我们不需要缓存的组件,它会根据组件的 name(不是路由的 name 哦) 选项进行匹配,所以组件如果想要条件性地被 KeepAlive 缓存,就必须显式声明一个 name 选项。

在 3.2.34 或以上的版本中,使用 <script setup> 的单文件组件会自动根据文件名生成对应的 name 选项,即使是在配合 <KeepAlive> 使用时也无需再手动声明。

在 3.2.34 以下的版本中,给 <script setup> 的单文件组件设置名字需要单独安装 unplugin-vue-macros 插件,才可以使用 defineOptions 这个宏,这个宏可以用来直接在 <script setup> 中声明组件名称

defineOptions({
  name: 'XXX'
})

 区分是“前进”还是“后退”

我上一篇文章:Vue 路由使用介绍以及添加转场动画 里面有讲到怎样区分是“前进”还是“后退”

条件判断

pinia 中用一个数组 excludeNames 存放要排除的组件 name,并添加一个用于更新的 updateExcludeRoutes 方法

<router-view v-slot="{ Component }">
  <keep-alive :exclude="store.excludeNames">
    <component :is="Component" />
  </keep-alive>
</router-view>
import { defineStore } from 'pinia'

export const useBaseStore = defineStore('base', {
  state: () => {
    return {
       excludeNames: [],
    }
  },
  actions: {
    updateExcludeRoutes(val) {
      if (val.type === 'add') {
        if (!this.excludeNames.find((v) => v === val.value)) {
          this.excludeNames.push(val.value)
        }
      } else {
        const resIndex = this.excludeNames.findIndex((v) => v === val.value)
        if (resIndex !== -1) {
          this.excludeNames.splice(resIndex, 1)
        }
      }
    }
  }
})

原理和路由转场动画的原理差不多
当我们 前进 时,从 excludeNames 数组中 删除 目的页面组件的 name 值,那么目的页面将被缓存
当我们 后退 时,从 excludeNames 数组中 添加 当前页面组件的 name 值,那么当前页面被不会被缓存

这里有点绕口,一定要区分清楚,应该删除哪个页面的 name 和应该添加哪个页面的 name,我用文字也没办法讲的太明白,大家结合代码理解下

Vue-Router 的路由守卫里面编写如下代码

router.beforeEach((to, from) => {
  const toDepth = routes.findIndex((v) => v.path === to.path)
  const fromDepth = routes.findIndex((v) => v.path === from.path)
  if (toDepth > fromDepth) {
    if (to.matched && to.matched.length) {
      const toComponentName = to.matched[0].components?.default.name
      baseStore.updateExcludeNames({ type: 'remove', value: toComponentName })
      // console.log('前进,删除', toComponentName)
    }
  } else {
    if (from.matched && from.matched.length) {
      const fromComponentName = from.matched[0].components?.default.name
      baseStore.updateExcludeNames({ type: 'add', value: fromComponentName })
      // console.log('后退,添加', fromComponentName)
    }
  }
  return true
})

总结

每次路由变更,路由守卫都会判断是前进还是后退,然后再对 excludeNames 数组进行删除或者添加 组件的 name

这样我们就实现了有条件的判断缓存,并且不需要修改任何组件的内部逻辑!

结束

以上就是文章的全部内容,感谢看到这里,希望对你有所帮助或启发!创作不易,如果觉得文章写得不错,可以点赞收藏支持一下,我会更新更多实用的前端知识与技巧,期待与你共同成长~