vue3.0+ts+element-plus多页签应用模板:多级路由缓存

Posted W先生-SirW

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue3.0+ts+element-plus多页签应用模板:多级路由缓存相关的知识,希望对你有一定的参考价值。

系列文章

一、先说点啥

1. 为啥需要路由缓存?

关于这个问题,我就只假设一个场景:用户正在填写一个很长很长的表单,然后他填着填着就需要查看一下其他页面表格中的信息,这个时候他一定会去切换路由。好的,等他看完回来,他发现自己刚才用了一个小时填了一半的表单,居然被清!空!了!

当然这只是个笑话,应该没有人会设计一个这样反人类的表单,但是如果这个系统有路由缓存的功能,那么刚才的悲剧是不是就可以避免了呢?所以,路由缓存是一个很有必要的功能。

2. 怎么实现路由缓存?

vue组件缓存:keep-alive => vue3.0官方文档 - keep-alive

路由缓存在vue中其实很容易实现,因为尤大大已经替我们写好了一个名叫keep-alive的组件,它作为一个vue的内置组件,可以在包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。这个描述是不是很符合我们想要实现的功能呢?说一下实现思路吧:

  1. 由于keep-alive组件在缓存多级路由的时候,表现并不好,所以我们需要将多级路由给它拍扁成二级路由,也就是路由扁平化
  2. 用keep-alive组件包裹住component组件(官方文档),用router-view包裹住component组件
  3. 每当切换路由时,向keep-alive组件的include属性中添加当前路由所对应的组件名
  4. 如果有无需缓存的路由,可在路由的meta中添加noKeepAlive: true属性,然后在切换路由时跳过当前组件

就这,是不是很简单?那么我们就去实现吧。

二、路由扁平化

首先第一步是实现路由扁平化,由于我的所有子级菜单项,都依赖于一个名叫AppMain的组件(不明白我在说什么的可以参照项目源码wyb199877/multi-tabs),也就是说,所有路由模块都没有直接引用Layout组件(你的主布局组件),而是引用了一个只有router-view的AppMain组件。所以,我们只需要将引用AppMain的中间路由删掉,就可以达到路由扁平化的目的了。所以,我们可以在你router的入口文件中这样写:

// 路由扁平化,将二级以上的路由转换成二级路由
const handleKeepAlive = (to: RouteLocationNormalized) => {
  // 判断目标路由记录是否大于2
  if (to.matched && to.matched.length > 2) {
    // 遍历路由记录,制作面包屑导航列表,并删除依赖于AppMain组件的中间路由,实现路由扁平化
    for (let i = 0; i < to.matched.length; i++) {
      const element = to.matched[i]
      /* 从这里开始处理面包屑 */
      if (to.meta.breadcrumb && element.name !== 'index') {
        ;(to.meta.breadcrumb as BreadcrumbItemProps[]).push({
          name: element.name!,
          label: element.meta.label as string,
          type: element.meta.type as MenuItemType
        })
      }
      if (!to.meta.breadcrumb) {
        to.meta.breadcrumb = []
      }
      /* 从这里结束处理面包屑 */
      if (element.components.default.name === 'AppMain') {
        to.matched.splice(i, 1)
        handleKeepAlive(to)
      }
    }
  } else {
    // 当路由已经扁平化完毕的时候,将目标路由自身加入面包屑中
    /* 从这里开始处理面包屑 */
    const toBreadcrumb = to.meta.breadcrumb as BreadcrumbItemProps[]
    if (!toBreadcrumb) return
    const isToInToBreadcrumb = toBreadcrumb.some(
      (item: BreadcrumbItemProps) => item.name === to.name
    )
    if (!isToInToBreadcrumb) {
      ;(to.meta.breadcrumb as BreadcrumbItemProps[]).push({
        name: to.name!,
        label: to.meta.label as string,
        type: to.meta.type as MenuItemType
      })
    }
    /* 从这里结束处理面包屑 */
  }
}

router.beforeEach((to, from, next) => {
  handleKeepAlive(to)
  next()
})

这样,我们就做好了路由扁平化,同时顺便处理了一下面包屑导航的定位(可以先忽略,等到面包屑章节的时候再回来看)。

三、定义tag模块处理路由缓存

我们需要在每次切换路由的时候,缓存路由对应的组件名,那么我们就可以在vuex中定义一个模块,专门用来处理标签页的增删和缓存。由于我在编写各个页面的时候,就规定组件名是对应路由名的首字母大写,所以这里的组件名只需要封装一个处理方法,将路由名的首字母大写即可。在src/store/modules下创建文件tag.ts

import { Module } from 'vuex'
import { RouteRecord, RouteRecordName } from 'vue-router'
import { TagStateProps, RootStateProps } from '../typings'

const tagModule: Module<TagStateProps, RootStateProps> = {
  namespaced: true,
  state: {
    cachedList: []
  },
  mutations: {
    ADD_CACHED_VIEW(state, routeName: string | RouteRecordName) {
      const bigName = Utils.getBigName(routeName as string)
      // 判断路由是否已经存在
      const isViewExist = state.cachedList.some((viewName) => viewName === bigName)
      // 路由不存在则将路由加入cachedList
      if (!isViewExist) {
        state.cachedList.push(bigName)
      }
    },
    REMOVE_CACHED_VIEW(state, routeName: string | RouteRecordName) {
      const bigName = Utils.getBigName(routeName as string)
      // 判断路由是否存在
      const isViewExist = state.cachedList.some((viewName) => viewName === bigName)
      if (!isViewExist) {
        return
      }
      // 存在就删除
      const viewIndex = state.cachedList.indexOf(bigName)
      state.cachedList.splice(viewIndex, 1)
    }
  }
}

export default tagModule

然后去入口文件中处理数据持久化(关于数据持久化请查看同系列文章element-plus按需引入与动态换肤):

// tag模块只需运行时存储
const vuexSession = new VuexPersist({
  storage: window.sessionStorage,
  key: 'vue-session',
  modules: ['tagModule']
})

const store = createStore<RootStateProps>({
  modules,
  plugins: [vuexLocal.plugin,vuexSession.plugin]
})

四、切换路由时加入缓存

现在,我们需要在切换路由的时候,将页面加入缓存。我们回到router的入口文件,修改router.beforeEach

router.beforeEach((to, from, next) => {
  if (!to.meta.noKeepAlive) {
    // 将路由缓存
    store.commit('tagModule/ADD_CACHED_VIEW', to.name)
  }
  // 路由扁平化
  handleKeepAlive(to)
  next()
})

这里解释一下上面的tagModule/ADD_VIEW:由于我们之前在定义模块的时候使用了namespace: true属性,所以当我们使用tag模块的mutation或action的时候,必须要加上tagModule作为命名空间。那么为什么非要是tagModule而不是tag呢?因为我在前面处理模块名的时候,规定了模块名必须是模块文件名加上Module,所以tag模块的模块名就是tagModule。

五、使用keep-alive

前面我们已经定义好了tagModule中的cacheList作为已经缓存的路由列表,现在我们只需将这个数组添加到keep-alive的include属性中就可以实现路由缓存了。

我们找到layout中的router-view,然后改成下面这样:

<router-view v-slot="{ Component }">
  <keep-alive :include="cachedList">
    <component :is="Component" :key="$route.name" />
  </keep-alive>
</router-view>
const cachedList = computed(() => store.state.tagModule.cachedList)
return { cachedList }

至此,我们的多级路由缓存就做好了。

下一篇预告:vue3.0+ts+element-plus多页签应用模板:头部工具栏(上)

以上是关于vue3.0+ts+element-plus多页签应用模板:多级路由缓存的主要内容,如果未能解决你的问题,请参考以下文章

vue3.0+ts+element-plus多页签应用模板:头部工具栏(中)

vue3.0+ts+element-plus多页签应用模板:侧边导航菜单(上)

vue3.0+ts+element-plus多页签应用模板:多级路由缓存

vue3.0+ts+element-plus多页签应用模板:如何优雅地使用Svg图标

vue3.0+ts+element-plus多页签应用模板:头部工具栏(上)

vue3.0+ts+element-plus多页签应用模板:前言