# 一、本质思考
在上一章节中,我们完成了 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)(默认子路由版)
# 三、路由缓存
对于嵌套路由,可不可以像动态组件的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
代码实现:
# 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
代码实现:
# 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 守卫处理