各位好,今天咱们来聊聊Vue Router里的“变脸术”——动态路由。也就是addRoute和removeRoute这两位大咖,以及它们是如何在运行时“动刀子”修改路由表的。准备好了吗?咱们这就开始!
开场:路由表的“户口本”
首先,得把路由表这玩意儿搞清楚。你可以把它想象成一个户口本,上面登记着所有路由的信息,包括路径(path)、组件(component)、名字(name)、子路由(children)等等。Vue Router就是靠这个户口本,才能知道用户访问哪个URL,该显示哪个组件。
这个户口本可不是一成不变的,某些场景下,我们需要根据用户的权限、数据或者其他条件,动态地添加或删除一些路由。比如,管理员才能看到后台管理页面,未登录用户不能访问用户中心。这时候,addRoute和removeRoute就派上用场了。
addRoute:路由表的“添丁进口”
addRoute方法,顾名思义,就是往路由表里添加新的路由。它有两种用法:
- 全局添加: 直接调用
router.addRoute(route),会将路由添加到根路由下。 - 指定父路由添加: 调用
router.addRoute(parentRouteName, route),会将路由添加到指定名字的父路由下。
我们先来看看addRoute的源码(简化版,只保留核心逻辑):
function addRoute(parentOrRoute, route) {
let parent;
let childRoute;
if (isRouteRecord(parentOrRoute)) { // 判断是否是RouteRecord
parent = parentOrRoute;
childRoute = route;
} else { // parentOrRoute是路由名字
parent = router.resolve({ name: parentOrRoute }).route;
childRoute = route;
}
if (!parent) {
// 找不到父路由
console.warn(`找不到父路由`);
return;
}
if (hasRoute(childRoute.name)) {
console.warn(`已经存在名为 ${childRoute.name} 的路由`);
return;
}
// 创建路由记录
const record = createRouteRecord(childRoute, parent);
// 将路由记录添加到父路由的children数组中
parent.children.push(record);
// 如果父路由有别名,也要添加到别名对应的路由记录的children中
if (parent.aliasOf) {
parent.aliasOf.children.push(record);
}
// 更新matcher(路由匹配器)
matcher.addRoute(record, parent);
// 触发导航守卫(如果有的话)
if (router.options.onReady) {
router.options.onReady();
}
}
这段代码做了几件事:
- 找到父路由: 判断传入的第一个参数是路由对象还是路由名字。如果是名字,就通过
router.resolve找到对应的路由记录。 - 创建路由记录: 调用
createRouteRecord将传入的路由配置转换成路由记录对象。这个路由记录对象包含了路由的所有信息,是路由表中的基本单元。 - 添加到父路由的children数组: 将新创建的路由记录添加到父路由的
children数组中,建立父子关系。 - 更新路由匹配器(matcher): 这是最关键的一步! Vue Router使用
matcher来进行路由匹配。matcher.addRoute会将新的路由记录添加到匹配器中,这样才能在后续的路由跳转中正确匹配到新的路由。 - 触发导航守卫: 如果配置了
onReady导航守卫,则触发它。
举个栗子:
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../components/Home.vue'
import About from '../components/About.vue'
const routes = [
{ path: '/', component: Home, name: 'Home' },
{ path: '/about', component: About, name: 'About' },
]
const router = createRouter({
history: createWebHistory(),
routes
})
// 动态添加一个路由
router.addRoute({ path: '/admin', component: () => import('../components/Admin.vue'), name: 'Admin' })
// 动态添加一个子路由
router.addRoute('About', { path: 'details', component: () => import('../components/AboutDetails.vue'), name: 'AboutDetails' })
export default router
这段代码,先定义了两个路由Home和About。然后,通过addRoute添加了一个新的路由Admin,以及一个About的子路由AboutDetails。
removeRoute:路由表的“清理门户”
removeRoute方法,用于从路由表中删除路由。它接收一个路由名字作为参数。
源码如下(简化版):
function removeRoute(name) {
if (!hasRoute(name)) {
console.warn(`不存在名为 ${name} 的路由`);
return;
}
const record = router.resolve({ name }).route;
const parent = record.parent;
if (parent) {
// 从父路由的children数组中删除
parent.children = parent.children.filter(child => child !== record);
} else {
// 如果没有父路由,说明是根路由,从路由表中删除
routes.value = routes.value.filter(route => route !== record); //routes.value是响应式数组
}
// 更新matcher
matcher.removeRoute(record);
// 触发导航守卫(如果有的话)
if (router.options.onReady) {
router.options.onReady();
}
}
这段代码的逻辑:
- 找到要删除的路由记录: 通过
router.resolve找到指定名字的路由记录。 - 从父路由的children数组中删除: 找到父路由,然后从其
children数组中移除要删除的路由记录。如果该路由没有父路由,则直接从根路由列表中删除。 - 更新matcher: 调用
matcher.removeRoute从路由匹配器中移除该路由,确保后续的路由跳转不会匹配到已删除的路由。 - 触发导航守卫: 如果配置了
onReady导航守卫,则触发它。
再来个栗子:
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../components/Home.vue'
import About from '../components/About.vue'
const routes = [
{ path: '/', component: Home, name: 'Home' },
{ path: '/about', component: About, name: 'About' },
{ path: '/admin', component: () => import('../components/Admin.vue'), name: 'Admin' } // 先定义admin路由
]
const router = createRouter({
history: createWebHistory(),
routes
})
// 动态删除admin路由
router.removeRoute('Admin')
export default router
这段代码先定义了一个包含Admin路由的路由表,然后通过removeRoute方法删除了Admin路由。
hasRoute:路由表的“人口普查”
hasRoute方法,用于判断路由表中是否已经存在指定名字的路由。
源码如下:
function hasRoute(name) {
return !!router.resolve({ name }).route;
}
实际上是使用router.resolve尝试解析该路由,如果能解析成功,就说明存在该路由,否则不存在。
运行时对路由表的修改:深入剖析
现在,我们来深入了解一下addRoute和removeRoute在运行时是如何修改路由表的。
核心数据结构:
routes: 一个响应式数组,存储了根路由的路由记录。RouteRecord: 路由记录对象,包含了路由的所有信息,例如path、component、name、children、parent等。matcher: 路由匹配器,负责将URL与路由记录进行匹配。
addRoute的修改过程:
-
创建
RouteRecord:addRoute首先会根据传入的路由配置创建一个RouteRecord对象。这个对象包含了路由的所有信息。属性 描述 path路由的路径。 component路由对应的组件。 name路由的名字,用于通过名字进行路由跳转。 children子路由的数组。 parent父路由的 RouteRecord对象。meta元数据,用于存储一些自定义的信息,例如权限、标题等。 alias路由的别名。 redirect重定向的路径。 beforeEnter路由独享的导航守卫。 props是否将路由参数作为props传递给组件。 instances一个对象,存储了该路由对应的组件实例。 leaveGuards一个数组,存储了该路由对应的离开守卫。 updateGuards一个数组,存储了该路由对应的更新守卫。 -
修改
routes或父路由的children: 如果添加的是根路由,则将新的RouteRecord添加到routes数组中。如果添加的是子路由,则将新的RouteRecord添加到父路由的children数组中。由于routes是一个响应式数组,所以修改它会触发Vue的响应式更新。 -
更新
matcher:matcher.addRoute会将新的RouteRecord添加到路由匹配器中。路由匹配器会根据路由的path生成匹配规则,例如正则表达式等,用于快速匹配URL。
removeRoute的修改过程:
-
查找
RouteRecord:removeRoute首先会根据路由名字查找对应的RouteRecord对象。 -
修改
routes或父路由的children: 如果删除的是根路由,则从routes数组中移除对应的RouteRecord。如果删除的是子路由,则从父路由的children数组中移除对应的RouteRecord。同样,由于routes是一个响应式数组,所以修改它会触发Vue的响应式更新。 -
更新
matcher:matcher.removeRoute会将对应的RouteRecord从路由匹配器中移除,防止后续的路由匹配到已删除的路由。
为什么需要更新matcher?
路由匹配器是Vue Router的核心组件,它负责将URL与路由记录进行匹配。如果没有更新matcher,那么即使你修改了routes数组,路由匹配器仍然会使用旧的路由表进行匹配,导致路由跳转出现问题。
动态路由的应用场景:
- 权限控制: 根据用户的权限动态添加或删除路由,实现不同用户访问不同页面的功能。
- 模块化开发: 将应用拆分成多个模块,每个模块定义自己的路由,然后在运行时动态加载这些路由。
- 插件系统: 允许插件动态添加或删除路由,扩展应用的功能。
- 动态菜单: 根据后台数据动态生成菜单,并根据菜单项动态添加路由。
注意事项:
- 路由名字的重要性:
addRoute和removeRoute都是通过路由名字来操作路由的,因此,为每个路由定义一个唯一的name非常重要。 - 性能问题: 频繁地添加和删除路由会影响性能,因此,应该尽量避免在运行时频繁地修改路由表。
- 导航守卫: 动态添加的路由也会触发导航守卫,因此,需要注意导航守卫的逻辑,避免出现问题。
- 嵌套路由: 在添加或删除嵌套路由时,需要特别注意父子关系,确保路由表的正确性。
总结:
addRoute和removeRoute是Vue Router提供的两个强大的API,它们允许我们在运行时动态地修改路由表,实现各种各样的动态路由场景。理解它们的实现原理,可以帮助我们更好地使用Vue Router,构建更加灵活和可扩展的应用程序。
好了,今天的“变脸术”讲座就到这里。希望大家有所收获!记住,路由表就像“户口本”,addRoute是“添丁进口”,removeRoute是“清理门户”。 掌握了这些,你也能成为Vue Router的“变脸大师”! 谢谢大家!