API
router.addRoute() 和 router.removeRoute()
如果新增加的路由与当前位置相匹配,就需要你用 router.push()
或 router.replace()
来手动导航,才能显示该新路由
并不是说不能使用导航守卫提供的
next
或return
重新导航后不能显示该新路由,而是router.push()
或router.replace()
可以在任何地方使用,不仅限于导航守卫。
router.push() 和 router.replace()
-
router.push(location):将路径添加到浏览器的历史记录中,进行导航。
-
router.replace(location):替换当前的历史记录条目,而不添加新的条目。
-
适用于非导航守卫中的动态导航。
-
需要手动控制导航流程。
-
可能导致导航守卫不一致(在某些复杂场景下)。
-
会重新执行导航守卫
删除路由
当路由被删除时,所有的别名和子路由也会被同时删除
可以通过
name
删除,所以请保持name
值唯一
添加嵌套路由
|
|
查看现有路由
Vue Router 提供了两个功能来查看现有的路由:
- router.hasRoute():检查路由是否存在。
- router.getRoutes():获取一个包含所有路由记录的数组。
手动导航的三种方式的区别
router.replace()
和next({ ...to, replace: true })
、return { ...to, replace: true }
router.replace()
和 next({ ...to, replace: true })
在功能上有些相似,但它们的使用场景和细节上有一些区别。
router.replace()
- 使用场景:
router.replace()
是一个方法,可以在任何地方使用,不仅限于导航守卫。它会立即执行导航,并用新的路由替换当前的路由,而不会在浏览历史中留下记录。 - 调用位置:可以在组件方法或导航守卫中使用。记住,如果你需要等待新的路由显示,可以使用
await router.replace()
。 - 行为:导航到新的路由并替换当前的路由,旧的路由不会出现在历史记录中。
|
|
NOTE: 在导航守卫中,更推荐使用
next
或return
来通过返回新的位置来触发重定向
next({ ...to, replace: true })
- 使用场景:
next()
是导航守卫(如beforeEach
)中的特有方法,用于控制导航的过程。next({ ...to, replace: true })
可以用来重定向并替换当前的路由。 - 调用位置:只能在导航守卫中使用。
- 行为:使用
next
方法继续导航过程,并指定重定向目标,同时使用replace: true
选项来替换当前的路由。
|
|
return { ...to, replace: true }
官网推荐使用return ...
替代next(...)
- 在导航守卫中,返回一个位置对象会告诉 Vue Router 要导航到新的位置。这个返回值会被 Vue Router 处理,并且重新触发导航守卫,使得整个导航过程重新运行。
- 自动重新触发导航守卫。
- 确保新路由生效,并且导航流程一致。
区别
-
调用位置:
router.replace()
可以在任何地方调用,包括组件内部的方法、导航守卫等。next({ ...to, replace: true })
只能在导航守卫中使用。
-
导航控制:
router.replace()
是一个独立的方法调用,立即执行导航。next({ ...to, replace: true })
是通过导航守卫控制导航流程的一部分。
-
返回方式:
router.replace()
直接执行,不返回控制权。next({ ...to, replace: true })
是在beforeEach
等导航守卫中用于控制导航流程的返回值。
代码示例
使用 router.replace()
:
|
|
使用 next({ ...to, replace: true })
:
|
|
总结
- 在导航守卫中,使用
next({ ...to, replace: true })
是更推荐的方式,因为它可以更好地控制导航流程。 - 在导航守卫之外,使用
router.replace()
。它是更通用的方法,可以的任何地方使用。
router.addRoute()
router.replace()
的应用场景
router.replace
,用于替换当前的路由。这与 router.push
类似,但不同的是,router.replace
不会向历史记录添加新条目,而是替换当前的历史记录条目。因此,当使用 router.replace
进行导航时,用户无法通过点击浏览器的“返回”按钮回到先前的页面。
以下是一些使用 router.replace
的典型场景:
-
登录和注销重定向: 当用户登录成功后,你可能希望将用户重定向到首页或特定的仪表盘页面,并且不希望用户通过浏览器的“返回”按钮回到登录页面。
1 2
// 登录成功后的处理 this.$router.replace({ name: 'dashboard' });
-
清理查询参数: 如果用户通过带有查询参数的链接访问某个页面,但在页面加载后这些查询参数已经不再需要,可以使用
router.replace
清理 URL。1 2 3 4
if (this.$route.query.token) { // 处理 token... this.$router.replace({ path: this.$route.path, query: null }); }
-
表单提交后的重定向: 在表单提交成功后,通常会将用户重定向到一个新页面。为了避免用户通过“返回”按钮重新提交表单,可以使用
router.replace
。1 2 3
this.submitForm().then(() => { this.$router.replace({ name: 'successPage' }); });
-
404 页面重定向: 当用户访问一个不存在的页面时,可以将用户重定向到 404 页面,并使用
router.replace
以防止用户在返回时再次看到错误页面。1 2 3 4
// 在路由守卫或组件中 if (this.pageNotFound) { this.$router.replace({ name: 'NotFound' }); }
-
基于条件的动态重定向: 在某些情况下,你可能会基于条件来重定向用户。例如,用户访问某个特定页面时,根据用户角色或权限进行重定向。
1 2 3
if (userRole !== 'admin') { this.$router.replace({ name: 'home' }); }
NOTE: 在导航守卫中更推荐
return ...
或next(...)
示例代码
|
|
总之router.replace
它在防止用户回到不希望显示的页面时非常有用。
动态调整应用的路由结构
在 Vue Router 的导航守卫中动态添加或删除路由是一个比较高级的用法,通常在需要基于某些条件(例如用户权限或角色)动态调整应用的路由结构时使用。
这个场景的核心思想是,导航守卫可以在导航过程中对路由进行修改,然后通过返回一个新的位置来触发重定向,而不是直接调用 router.replace()
。这是因为在导航守卫中直接调用 router.replace()
可能会导致导航重复触发,从而引起无限循环的问题。
示例场景解释
假设我们有一个应用,其中某些路由只有在特定条件下才存在(例如,用户登录后才可访问的管理页面)。在用户访问某个路由时,我们需要检查该路由是否已经存在,如果不存在则动态添加,然后重定向到该路由。
代码示例
|
|
详细解释
-
检查路由是否存在: 在导航守卫中首先检查目标路由是否已经存在,使用
router.getRoutes()
获取当前所有路由,然后通过some
方法判断目标路由是否存在。 -
动态添加路由: 如果目标路由不存在,则通过
generateRoute(to)
动态生成新的路由配置,并使用router.addRoute()
将新路由添加到路由表中。 -
触发重定向: 通过返回新的位置(即
next({ ...to, replace: true })
)来触发重定向。这将确保当前导航被取消,然后重新发起一次导航到新的路由,同时使用replace: true
确保不会向浏览器的历史记录添加新条目。 -
正常导航: 如果目标路由已经存在,则调用
next()
进行正常导航。
注意事项
- 避免无限循环:
通过返回新的位置触发重定向,而不是直接调用
router.replace()
,可以有效避免导航守卫中的无限循环问题。 - 异步加载组件: 动态添加的路由可以使用异步组件加载,以确保在路由被访问时组件才会被加载,优化应用性能。
通过这种方式,可以在导航过程中根据实际需要动态调整路由结构,确保用户始终能够访问到最新的路由配置。
router.replace()
理解如何通过返回新的位置触发重定向而避免无限循环问题,可以从 Vue Router 的导航守卫执行逻辑入手。
导航守卫中的无限循环问题
在导航守卫中直接调用 router.replace()
可能会导致无限循环。这是因为 router.replace()
会立即触发一次新的导航,而这个新的导航会再次触发导航守卫,导致导航守卫再次执行 router.replace()
,从而形成无限循环。
通过返回新的位置避免无限循环
当我们在导航守卫中返回新的位置而不是直接调用 router.replace()
时,Vue Router 会认为这是一次新的导航请求。这个新的导航请求会重新评估所有导航守卫,但因为我们在返回新的位置时不会立即触发导航(而是等待当前导航流程完成),所以不会形成无限循环。
代码解析
下面是一个具体的代码示例以及解析:
|
|
详细解析
-
判断目标路由是否存在:
- 使用
router.getRoutes()
获取当前所有路由,并通过some
方法判断目标路由是否存在。
- 使用
-
动态添加路由:
- 如果目标路由不存在,使用
generateRoute(to)
动态生成新的路由配置,并使用router.addRoute(newRoute)
将新路由添加到路由表中。
- 如果目标路由不存在,使用
-
返回新的位置:
- 通过
next({ ...to, replace: true })
返回新的位置。这告诉 Vue Router 当前导航已经结束,并需要导航到新的位置。 replace: true
确保不会向浏览器的历史记录添加新条目。
- 通过
-
重新触发导航守卫:
- 返回新的位置后,Vue Router 会重新评估导航守卫。但由于我们不是立即触发导航,而是等待当前导航流程完成,所以不会出现无限循环问题。
-
正常导航:
- 如果目标路由已经存在,直接调用
next()
进行正常导航。
- 如果目标路由已经存在,直接调用
避免无限循环的关键点
-
等待当前导航流程完成: 返回新的位置而不是立即调用
router.replace()
允许当前的导航流程完成后再触发新的导航。这避免了直接在导航守卫中嵌套触发新的导航请求,避免了无限循环。 -
返回新的位置: 返回新的位置通过
next({ ...to, replace: true })
告诉 Vue Router 重定向到新的位置,而不是立即发起一个新的导航请求。这样可以确保新的导航流程在当前流程完成后才会开始。
通过这种方式,可以动态调整路由配置,同时避免导航守卫中的无限循环问题。无限循环问题
路由记录和元信息
Vue Router 的标准路由记录和 meta
类型是设计用来管理和配置路由信息的。这些设计使得在应用中处理路由变得更加灵活和强大。以下是对 Vue Router 的标准路由记录和 meta
类型的详细说明:
标准路由记录类型 (RouteRecordRaw
)
RouteRecordRaw
是 Vue Router 中定义路由的基本类型。它用于定义一个路由配置的原始记录。主要字段包括:
-
path
:string
- 路由的路径。可以是静态路径或者带有动态参数的路径(例如
/user/:id
)。
- 路由的路径。可以是静态路径或者带有动态参数的路径(例如
-
name
:string | undefined
- 路由的名称。这个名称用于路由导航和重定向。它可以是
undefined
。
- 路由的名称。这个名称用于路由导航和重定向。它可以是
-
component
:Component | AsyncComponent | undefined
- 这个路由匹配时加载的组件。可以是一个组件对象,也可以是异步组件加载函数。
-
redirect
:string | Location | Function | undefined
- 路由重定向的目标路径。如果设置了重定向,当访问这个路由时,会自动导航到指定的目标路径。
-
children
:RouteRecordRaw[] | undefined
- 子路由配置。这个路由的子路由配置可以通过嵌套的
RouteRecordRaw
数组来定义。如果没有子路由,这个字段可以是undefined
。
- 子路由配置。这个路由的子路由配置可以通过嵌套的
-
meta
:Record<string, any>
- 路由的元信息。
meta
是一个对象,可以包含任意的自定义信息,比如权限、标题等。这些信息不会影响路由的匹配,而是用于应用的其他逻辑。
- 路由的元信息。
-
beforeEnter
:RouteEnterGuard | undefined
- 进入路由前的导航守卫函数。用于在路由进入前进行权限检查或其他逻辑处理。
-
props
:boolean | object | Function | undefined
- 控制如何将路由参数传递给组件。可以是布尔值、对象、函数或者
undefined
。
- 控制如何将路由参数传递给组件。可以是布尔值、对象、函数或者
meta
类型
meta
是路由记录中的一个字段,用于存储与路由相关的任意元数据。你可以根据需要自定义 meta
的内容。常见的字段包括:
-
title
:string | undefined
- 路由的标题,通常用于页面标题或面包屑导航。
-
requiresAuth
:boolean | undefined
- 是否需要认证。用于控制访问权限,比如只有经过认证的用户才能访问该路由。
-
roles
:string[] | undefined
- 访问该路由需要的角色列表。用于权限控制。
-
icon
:string | undefined
- 路由的图标。通常用于侧边栏或导航菜单。
-
hidden
:boolean | undefined
- 是否在导航菜单中隐藏该路由。
-
keepAlive
:boolean | undefined
- 是否缓存该路由组件。通常用于优化性能,避免重复加载组件。
-
alwaysShow
:boolean | undefined
- 在多级路由中,是否始终显示该路由的父级菜单。
-
activePath
:string | undefined
- 用于设置路由激活状态的路径,通常用于动态高亮菜单项。
-
showLink
:boolean | undefined
- 是否在导航菜单中显示该路由。
示例
|
|
总结
RouteRecordRaw
类型定义了一个路由记录的基本结构,包括路径、名称、组件、重定向、子路由、元信息等。meta
字段用于存储与路由相关的任意元数据,方便应用进行各种自定义配置和逻辑处理。
懒加载不使用defineAsyncComponent
根据警告信息,Vue Router 建议使用懒加载组件时直接传递 import
语句,而不是使用 defineAsyncComponent
。这是因为 Vue Router 内部会处理组件的懒加载逻辑,因此不需要在外部包装一层 defineAsyncComponent
。
修改代码
将 defineAsyncComponent
替换为直接使用 import
语句即可。
修改前
假设你在路由配置中使用 defineAsyncComponent
,代码可能如下:
|
|
修改后
将 defineAsyncComponent
替换为直接使用 import
:
|
|
示例
下面是一个完整的路由配置文件示例:
|
|
总结
将 defineAsyncComponent
替换为直接使用 import
语句以避免警告,并简化路由配置。这样可以更好地遵循 Vue Router 的建议和最佳实践。
动态导入组件
使用 import.meta.glob
是处理动态导入的一个有效方法,它可以在构建时自动解析和加载匹配的模块。这种方式是 Vite 提供的,用于处理动态模块导入,支持静态分析和构建优化。它适用于你需要根据路径动态加载组件的情况。
使用 import.meta.glob
示例代码
-
定义和使用动态模块导入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
// utils.ts import { log } from "@/plugins/logger"; import { MenuItem } from "@/types/component/menuItem"; import { RouteRecordRaw } from "vue-router"; // 使用 import.meta.glob 加载所有 Vue 组件 const modules = import.meta.glob('@/views/**/*.vue'); // 根据路径加载组件 function loadComponent(componentPath: string) { const path = `@/views/${componentPath}`; if (modules[path]) { return modules[path]; } else { return () => Promise.reject('Component not found'); } } // 转换 MenuItem 到 RouteRecordRaw function convertToRoute(menuItem: MenuItem): RouteRecordRaw { const route: RouteRecordRaw = { path: menuItem.path, name: menuItem.name, component: menuItem.component ? loadComponent(menuItem.component) : undefined, redirect: menuItem.redirect, meta: menuItem.meta as Record<string, any>, // 递归调用convertToRoute children: menuItem.children ? menuItem.children.map(convertToRoute) : [] }; return route; } // 遍历路由数据并添加到 router export function addRoutes(menus: MenuItem[]) { menus.forEach((menuItem) => { const route = convertToRoute(menuItem); if (route.name && !router.hasRoute(route.name)) { router.addRoute(route); log("动态添加路由: name= %s, path= %s", route.name, route.path); } }); }
说明
import.meta.glob
:这个方法会返回一个包含匹配文件的对象,键是文件路径,值是一个函数,这个函数会返回一个动态导入的 Promise。loadComponent
函数:通过检查modules
对象中是否包含指定路径来动态加载组件。如果路径存在于modules
中,它将返回对应的模块,否则返回一个拒绝的 Promise。
注意事项
-
路径匹配: 确保
import.meta.glob
中的路径匹配模式正确。@/views/**/*.vue
匹配src/views
目录下的所有 Vue 文件及其子目录。 -
路径映射: 路径应与
import.meta.glob
中定义的匹配模式一致。@/views/${componentPath}
应该和实际路径匹配。 -
组件导入: 确保
modules
中定义的路径与实际组件路径一致,否则会导致找不到组件。 -
构建优化:
import.meta.glob
在构建时会优化模块加载,避免在运行时解析路径。
通过这种方式,你可以在运行时动态加载和注册 Vue 组件,适合于需要动态导入大量组件的场景。