1. 动态路由
1.1 动态路由匹配
路由分为静态路由和动态路由。上面讲过的类似 ‘/login’ 这样写死的就是静态路由。
动态路由通过在路径中使用一个动态字段(简称:路径参数),来将不同的信息映射到同一个组件中。
如:有一个 User 组件用于渲染所有用户,但这些用户的 ID 是不同的
# User.vue
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
console.log(router.params)
</script>
<template>
<div>
{
{ $route.params.id }}
</div>
</template>
import { createRouter, createWebHistory } from 'vue-router'
import Login from '@/views/Login.vue'
const routes = [
{
path: '/',
redirect: '/Login'
},
{
path: '/login',
name: 'login',
component: Login,
meta: {
title: '登录',
requiresAuth: false // 设置为 false 或 不设置代表不需要校验身份,任何人都可以访问这个页面
}
},
{
path: '/user/:id',
name: 'user',
component: () => import('@/views/User.vue'),
meta: {
title: '用户管理', // 页面标题
requiresAuth: true // 登录身份校验成功,才可以访问这个页面
}
}
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes
})
router.beforeEach((to, from) => {
if(to.meta.title) document.title = to.meta.title
})
export default router
- meta 路由元信息:可用于给路由附加如:过渡名称、访问路由权限、页面标题等任意信息。可以通过路由地址或导航守卫进行访问。
- router.beforeEach((to,from) => {}) 注册一个全局前置守卫,它会在每次导航之前被执行。
- to 即将要进入的目标
- from 当前导航正要离开的路由
- 路径参数用冒号
:
表示。当一个路由被匹配时,它的 params 值将在每个组件中以$route.params
的形式暴露出来。
1.2 动态路由另外一种配置方式
可以通过 children 配置动态参数
const routes = [
// ...其它路由参数
{
path: '/user',
name: 'user',
children: [
{
path: ':id',
component: () => import('@/views/User.vue'),
meta: {
title: '用户管理',
requiresAuth: true
},
},
{
path: 'Add',
component: () => import('@/views/UserAdd.vue'),
meta: {
title: '用户新增',
requiresAuth: true
}
}
]
}
]
- 以
/
开头的嵌套路径将被视为根路径。这允许你利用组件嵌套,而不必使用嵌套的 URL。
1.3 路由的匹配语法——在参数中自定义正则
两个路由 /:orderId
和 /:productName
,两者会匹配完全相同的 URL,所以我们需要一种方法来区分它们。
假设:orderId
总是一个数字,而 productName
可以是任何东西,所以我们可以在括号中为参数指定一个自定义的正则
const routes = [
// /:orderId -> 仅匹配数字
{ path: '/:orderId(\\d+)' },
// /:productName -> 匹配其他任何内容
{ path: '/:productName' },
]
这时,跳转到 /25
将匹配 /:orderId
,其他情况将会匹配 /:productName
。
确保转义反斜杠( \
),就像我们对 \d
(变成\\d
)所做的那样,在 JavaScript 中实际传递字符串中的反斜杠字符。
更多路由匹配语法详见 Vue Router 官网。
1.4 对路由的增删改操作API
对路由的添加通常是通过 routes
选项 来完成的,但是在某些情况下,你可能想在应用程序已经运行的时候添加或删除路由:
- 添加路由 router.addRoute()
- 移除路由 router.removeRoute()
// 添加路由
router.addRoute({ path: '/about', component: About })
// 我们也可以使用 this.$route 或 route = useRoute() (在 setup 中)
router.replace(router.currentRoute.value.fullPath)
// 删除路由
router.removeRoute('about')
如果你需要等待新的路由显示,可以使用
await router.replace()
2. 命名视图
如果想在同一个界面中定义多个同级视图,就需要给 router-view
设置名字(默认 default),这样就可以有多个出口了。
<router-view class="view left-sidebar" name="LeftSidebar"></router-view>
<router-view class="view main-content"></router-view>
<router-view class="view right-sidebar" name="RightSidebar"></router-view>
一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components
配置 (带上 s):
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
components: {
default: Home,
// LeftSidebar: LeftSidebar 的缩写
LeftSidebar,
// 它们与 `<router-view>` 上的 `name` 属性匹配
RightSidebar,
},
},
],
})
更高级的嵌套命名视图如下,可以尝试着自己分析写一下代码:
/settings/emails /settings/profile
+-----------------------------------+ +------------------------------+
| UserSettings | | UserSettings |
| +-----+-------------------------+ | | +-----+--------------------+ |
| | Nav | UserEmailsSubscriptions | | +------------> | | Nav | UserProfile | |
| | +-------------------------+ | | | +--------------------+ |
| | | | | | | | UserProfilePreview | |
| +-----+-------------------------+ | | +-----+--------------------+ |
+-----------------------------------+ +------------------------------+
Nav
只是一个常规组件。UserSettings
是一个视图组件。UserEmailsSubscriptions
、UserProfile
、UserProfilePreview
是嵌套的视图组件。
3 路由组件传参
带路由的项目组件中,使用 $route 会与路由紧密耦合,这限制了组件的灵活性,使得它只能用于特定的 URL。
如果想要解除这种行为,可以通过 props 进行配置
3.1 路由 props 属性值为 true
# 路由配置代码
{
path: '/user',
name: 'user',
component: () => import('@/views/User.vue'),
children: [
{
path: ':id',
component: () => import('@/views/UserHome.vue'),
// 路由配置 props:true,这样映射的组件中可以通过 props 定义对应的路由参数,从而达到$route对象和组件解耦的效果
props: true
}
]
}
# 路由映射组件代码
<script setup>
import { defineProps } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
// 组件 props 中定义路由参数对应的属性
const props = defineProps(['id'])
</script>
<template>
<div>
// 通过 $route 对象调用路由参数
<em> {
{ $route.params.id }} </em>
<br>
// 通过 props 中定义的属性调用路由参数
<strong>{
{ props.id }}</strong>
</div>
</template>
- 当路由配置属性
props
设置为true
时,route.params
将被设置为组件的 props- 路由组件传参的好处是:使组件更容易重用和测试。
如果路由中含有命名视图,那么你必须为每个命名视图定义 props 配置:
const routes = [
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
3.2 路由 props 属性值为一个普通对象
当 props
是一个对象时,它将原样设置为组件 props。当 props 是静态的时候很有用。
# 路由代码
{
path: '/user',
name: 'user',
component: () => import('@/views/User.vue'),
children: [
{
path: ':id',
component: () => import('@/views/UserHome.vue'),
// props 是一个对象,会将这个对象传递给对应的组件。props 中无法获取路由参数
props: { num: 1234567890 }
}
]
}
# 映射组件的代码
<script setup>
import { defineProps } from 'vue'
const props = defineProps(['id', 'num'])
console.log(props, 11111111111) //Proxy(Object) {num: 1234567890, id: undefined} 11111111111
</script>
<template>
<div>
<em> {
{ $route.params.id }} </em>
<br>
<strong>{
{ props.id }}</strong> <!-- props 中无法获取路由参数 -->
<hr>
<ins>{
{ props.num }}</ins>
</div>
</template>
3.4 路由 props 属性值为一个函数
你可以创建一个返回 props 的函数。这允许你将参数转换为其他类型,将静态值与基于路由的值相结合等等。
const routes = [
{
path: '/search',
component: SearchUser,
props: route => ({ query: route.query.q })
}
]
URL /search?q=vue
将传递 {query: 'vue'}
作为 props 传给 SearchUser
组件。
4. history 历史模式
在创建路由器实例时,history
配置允许我们在不同的历史模式中进行选择。
-
Hash 模式
hash 模式是用
createWebHashHistory()
创建的,不依赖于服务器配置。但不会被搜索引擎处理,SEO效果较差。import { createRouter, createWebHashHistory } from 'vue-router' const router = createRouter({ history: createWebHashHistory(), routes: [ //... ], })
createWebHashHistory(base?)
参数为一个路径// 基于 https://example.com/folder 的 hash 模式 createWebHashHistory() // 给出一个 `https://example.com/folder#` 的 URL createWebHashHistory('/folder/') // 给出一个 `https://example.com/folder/#` 的 URL
-
HTML5 模式
HTML5 模式使用
createWebHistory()
创建 ,需要正确的配置服务器。这种方式会被搜索引擎处理,SEO效果较好。是单页应用最常见的模式。如果 URL 不匹配任何静态资源,它应提供与你的应用程序中的
index.html
相同的页面。各服务器配置示例import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory(), routes: [ //... ] })
当使用这种历史模式时,URL 会看起来很 “正常”,例如
https://example.com/user/id
。
万能路由显示 404 页面:
const router = createRouter({
history: createHistory(),
routes: [{ path: '/:pathMatch(.*)', component: NotFoundComponent }]
})
5 路由守卫
Vue Router 提供了多种路由守卫,如:全局路由守卫、组件内路由守卫或单个路由独享守卫。它们主要通过跳转或取消的方式守卫导航。
5.1 全局前置守卫
全局前置守卫通过 router.beforeEach(callback(to, from)) 注册。参数是一个守卫方法,其接收两个参数:
- to 即将要进入的目标
- from 当前导航正要离开的路由
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中。
可以返回的值:
-
false:用于取消当前导航。
const router = createRouter({ ... }) router.beforeEach((to, from) => { // ... // 返回 false 以取消导航 return false })
-
一个路由地址:如同调用 router.push() 一样,可以跳转到这个指定的路由地址,可以在返回对象中通过如:replace:true 或 name: ‘home’ 之类的属性来指定这个路由地址。
router.beforeEach(async (to, from) => { if ( // 检查用户是否已登录 !isAuthenticated && // 避免无限重定向 to.name != 'Login' ) { // 将用户重定向到登录页面 return { name: 'Login' } } })
router.beforeEach(async (to, from) => { // canUserAccess() 返回 `true` 或 `false` const canAccess = await canUserAccess(to) if (!canAccess) return '/login' })
5.2 全局解析守卫
全局解析守卫通过 router.beforeResolve 注册。解析守卫刚好会在每次导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。
5.3 全局后置钩子
后置钩子不会改变导航本身。对于分析、更改页面标题、声明页面等辅助功能以及许多其它事情很有用。
5.4 路由独享守卫
beforeEnter 守卫可直接在路由上进行配置,只在进入路由时触发。而 params、query、hash 改变时不会触发。
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// 返回 false 用于取消当前导航
return false
}
}
]
beforeEnter 守卫也可以是一个函数数组。
5.5 组件内守卫
顾名思义,就是在组件内直接定义路由导航守卫,会传递给路由配置。
路由组件可以添加如下守卫(setup 组合式 API):
-
onBeforeRouteUpdate 在当前路由改变且该组件被复用时调用。
举例来说,对于一个带有动态参数的路径
/users/:id
,在/users/1
和/users/2
之间跳转的时候,由于会渲染同样的UserDetails
组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 -
onBeforeRouteLeave 在导航离开渲染组件对应的路由时调用。
这个 离开守卫 通常用来预防用户在还未保存修改前突然离开。该导航可以通过返回
false
来取消。
示例代码:
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
import { ref } from 'vue'
// 与 beforeRouteLeave 相同,无法访问 `this`
onBeforeRouteLeave((to, from) => {
const answer = window.confirm('确定要离开当前页面? 你并未保存更改信息哦!')
// 取消导航并停留在同一页面上
if (!answer) return false
})
const userData = ref()
// 与 beforeRouteUpdate 相同,无法访问 `this`
onBeforeRouteUpdate(async (to, from) => {
//仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改
if (to.params.id !== from.params.id) {
userData.value = await fetchUser(to.params.id)
}
})
5.6 完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用
onBeforeRouteLeave
守卫。 - 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
onBeforeRouteUpdate
守卫(2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
(选项式API)。 - 调用全局的
beforeResolve
守卫(2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter
(选项式API)守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入。
注:在组件卸载时,所有路由守卫会被移除。