# 一、本质思考

  在上一章节中,我们完成了 vue-router 的简单使用,但还是有一些问题是值得我们思考的。

  • 在进行页面切换时,vue-router 发生了什么?
  • 它和动态路由的实现有什么区别?

# 1、生命周期

  我们可以在 page 文件夹下随便找一个路由组件增加如下内容进行测试:

export default {
  name: 'PartThree',
  beforeDestroy() {
    console.log('PartThree组件即将被销毁')
  },

  mounted() {
    console.log('PartThree组件挂载完毕了')
  }
}

# 2、$route 与 $router

  我们打印一下,看看 vc 中有什么变化:

// PartOne.vue
export default {
  name: 'PartOne',
  mounted() {
    console.log('PartOne组件的vc', this)

    window.oneRouter = this.$router
  }
}

// PartTwo.vue
export default {
  name: 'PartTwo',
  mounted() {
    console.log('PartTwo组件的vc', this)

    window.twoRouter = this.$router
  }
}

  比较分析可以发现:

间接验证了上一章节中提到的:多个路由 route 可以由一个路由器 router 控制

提示

  如果我们打印 this.$route,得到的是与当前路由组件相关的路由规则。

# 二、嵌套路由

  什么是嵌套路由?如图所示:

代码演示

组件切换案例 (opens new window)(嵌套路由版)

记忆

  简单的说,就是 page 文件夹中 —— 一个路由组件使用了另一个路由组件做为它的路由组件

  但通常的应用场景为:

<!-- 1、跳转控制区域 --> 一般通过 v-for 进行实现

代码演示

后台管理系统 之 多级导航栏

# 1、代码实现

① 路由配置

export default new VueRouter({
  routes: [
    {
      path: '/',
      redirect: '/part1'
    },
    {
      path: '/part1',
      component: () => import('../page/PartOne.vue'),

      // 二级路由
      children: [
        {
          // 注意二级路由是✨不用加'/'的
          path: 'one',
          component: () => import('../page/One.vue')
        },
        {
          path: 'two',
          component: () => import('../page/Two.vue')
        }
      ]
    }
    // ……
  ]
})

② 使用示例

  • 路由组件 PartOne.vue
<template>
  <div>
    <p>
      开辟鸿蒙,谁为情种?都只为风月情浓。趁着这奈何天,伤怀日,寂寥时,试遣愚衷。因此上,演出这怀金悼玉的《红楼梦》。
    </p>

    <!-- 在当前路由组件PartOne中展示新的路由组件 -->

    <!-- 1、跳转控制区域 -->
    <span><router-link to="/part1/one">one</router-link></span>
    <span><router-link to="/part1/two">two</router-link></span>

    <!-- 2、页面展示区域 -->
    <div class="main">
      <router-view></router-view>
    </div>
  </div>
</template>

# 2、二级重定向

  和上一章一样,如果我们不使用重定向,那么页面渲染完时,路由组件 PartOne 下的<router-view>并没有页面显示。

原因:

刚进入 HomeView 组件时,路径会重定向到我们设置的/part1,而 vue-router 并没有检测到路由组件 PartOne 的相关子路由配置。

解决:

export default new VueRouter({
  routes: [
    {
      path: '/',
      redirect: '/part1'
    },
    {
      path: '/part1',
      component: () => import('../page/PartOne.vue'),

      // 二级路由重定向
      redirect: '/part1/one',

      children: [
        {
          path: 'one',
          component: () => import('../page/One.vue')
        },
        {
          path: 'two',
          component: () => import('../page/Two.vue')
        }
      ]
    }
    // ……
  ]
})

# 3、默认子路由

  在涉及多层子路由时,处理可以使用重定向方式解决初次渲染无内容问题外,还可以使用默认子路由的方式,它在二级子路由甚至多级子路由中应用广泛。

  下面我们就以常见的后台管理系统为例,对重定向和默认子路由的方式进行比较:

# ① 重定向方式

优点:

层次清晰,to 属性使用时,同级名称基本一致

代码实现

后台管理系统 (opens new window)(重定向版)

# ② 默认子路由方式

优点

让路由配置得到了极大的简化

代码实现:

后台管理系统 (opens new window)(默认子路由版)

# 三、路由缓存

  对于嵌套路由,可不可以像动态组件的keep-alive一样,进行路由组件缓存,用于提升用户的体验?

答案

  完全可以,并且和动态路由一样的使用方式:

<!-- 1、动态组件(保持组件状态) -->
<div class="main">
  <keep-alive>
    <component :is="componentS"></component>
  </keep-alive>
</div>

<!-- 1、嵌套路由(保持路由组件状态) -->
<div class="main">
  <keep-alive include="PartOne">
    <router-view></router-view>
  </keep-alive>
</div>

  但有时,我们也会遇到使用 <keep-alive> 不生效的情况。如:上面的两个后台管理系统案例中,由于涉及了多层(>=2 层)<router-view> 嵌套,导致路由组件缓存并没有成功。

# 1、路由扁平化 ✨

  不生效的原因是因为存在多层 <router-view> 嵌套,那我们可以把所以涉及<router-view>的组件(Layout.vue、HomeView.vue、PartOne.vue)功能化,提取出来作为一个‘中间件’组件 RouterView.vue。

# ① 步骤 1

  先使用默认子路由,构建极简的、方便扁平化处理的路由结构,然后利用递归结合 vue-router 的 API 方法将其扁平化构建。

结果:

所有的路由组件都都可以进行直接缓存了。

// routes树形列表
export const routes = {
  name: 'backend',
  path: '/',
  component: () => import('../view/Layout.vue'),

  children: [
    // ……
  ]
}

// 老大router
const router = new VueRouter({
  routes: []
})

// 扁平化操作
const flatRouter = (routes, newRoutes = []) => {
  routes.forEach((item) => {
    if (item.children && item.children.length > 0) {
      flatRouter(item.children, newRoutes)
    } else {
      newRoutes.push(item)
    }
  })
  return newRoutes
}
routes.children = flatRouter(routes.children)
router.addRoute(routes)

export default router

# ② 步骤 2

  我们可以结合 meta 属性控制哪些路由组件需要进行缓存。

  • Layout.vue
<template>
  <div class="home">
    <h3>后台管理系统经典布局</h3>

    <div class="main">
      <keep-alive :include="caches">
        <router-view></router-view>
      </keep-alive>
    </div>
  </div>
</template>

<script>
  import { routes } from '@/router/index.js'

  export default {
    name: 'Layout',
    computed: {
      cachesRouteArr() {
        const routesArr = routes.children
          .filter((item) => item.meta.keepAlive)
          .map((item1) => item1.name)
        return routesArr
      }
    }
  }
</script>
完整代码实现

  这个方案是大多数后台管理系统对对页面进行缓存的解决方式:如vue-element-plus-admin (opens new window)

参考:

https://juejin.cn/post/7099355285172518920

代码实现:

多级路由缓存-方案 1 (opens new window)

# 2、菜单和路由分离

  类似的问题在 vue-element-admin 的issues (opens new window)中也有提及到。

  把显示的菜单和实际的路由分离开就,菜单的显示继续用多级的菜单数据, 然后 router 里面实际添加的数据进行格式化一下,全部转换成一级菜单, 这样就不涉及到父菜单需要用 router-view 来显示子级菜单内容, 所有的页面都共用的一个 router-view, 就没这个缓存的问题了。

# ① 步骤 1

  首先将配置好的 router,用 vuex 缓存起来,用作菜单的显示

# ② 步骤 2

  然后把 router 转换一下,全部转换成一级,再 router.addRoutes, 这样菜单的显示和实际操作的路由就分离了,一个是多级,一个是一级。

添加多个路由

  在 Vue Router 4 中,router.addRoutes() 方法已经被废弃了,但是在 Vue Router 3 中,这个方法仍然可用。

官方描述:https://v3.router.vuejs.org/zh/api/#router-getroutes

const router = new VueRouter({
  mode: 'history',
  routes: [
    // 初始路由配置
  ]
})

// 动态添加多个路由
const routesToAdd = [
  //
]

// 方式1
router.addRoutes(routesToAdd)

// 方式2
routesToAdd.forEach((route) => {
  router.addRoute(route)
})

至于面包屑导航,是在 router.afterEach, 通过 route.name 查找了一遍 菜单的数据重新生成的。

完整代码实现

  这个方案是大多数后台管理系统对对页面进行缓存的解决方式:如vue-element-admin (opens new window)

参考:

https://juejin.cn/post/6844903968125124616

代码实现:

多级路由缓存-方案 2

# 3、按需缓存视图组件

https://juejin.cn/post/6844903846901186574
https://juejin.cn/post/6976814768812195854
https://juejin.cn/post/6844904186249740301

  通常在移动端的要求:进入缓存组件,返回销毁组件。

假设在一个列表中,用户滑动几页点击了详情,此时若再回到列表页,页面状态都已经刷新,用户又需要再进行滑动,这显然是不合理的

<keep-alive>
  <!-- 需要缓存的视图组件 -->
  <router-view v-if="$route.meta.keepAlive"> </router-view>
</keep-alive>

<!-- 不需要缓存的视图组件 -->
<router-view v-if="!$route.meta.keepAlive"> </router-view>

# 4、其他

  巧用声明周期钩子 和 路由守卫钩子等到达目标需求:

  • beforeRouteLeave 守卫处理
更新于 : 8/7/2024, 2:16:31 PM