各位好,今天咱们来聊聊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的“变脸大师”! 谢谢大家!