一、动态路由匹配
1.带参数的动态路由匹配
import User from './User.vue'
// 这些都会传递给 `createRouter`
const routes = [
// 动态字段以冒号开始
{ path: '/users/:efg', component: User },
]
这种方式的路由会匹配到/users/abc
或者/users/123
,路径参数用冒号:
表示,并且将值abc
放在route.params
里面
//当前路由/users/abc
import {useRoute} from "vue-router";
const route = useRoute();
console.log(route.params,"route"); // {efg:abc}
注意: 这种路径匹配不上 /users/abc/name
,需要把path修改为/users/:efg/name
const routes = [
// 动态字段以冒号开始
{ path: '/users/:efg/name', component: User }, //这样能匹配上,
]
console.log(route.params,"route"); //{efg:abc}
//或者下面这种写法====================================================================
const routes = [
// 动态字段以冒号开始
{ path: '/users/:efg/:hhh', component: User }, //这样能匹配上,
]
console.log(route.params,"route"); //{efg:abc,hhh:name}
具体匹配规则
匹配模式 | 匹配路径 | route.params |
---|---|---|
/users/:username | /users/eduardo | { username: ‘eduardo’ } |
/users/:username/posts/:postId | /users/eduardo/posts/123 | { username: ‘eduardo’, postId: ‘123’ } |
/users-:id(.*) | /users-/123 | { id: ‘123’ } |
需要注意的是,如果路由从/users/abc
变到/users/123
,因为都是用的同一个component
,所以会重复使用,并不会销毁,这就会导致页面不会重新创建,不会触发生命周期钩子,也获取不到最新的route.params
,需使用下面的方法来解决
<script setup>
import { watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
watch(() => route.params.id, (newId, oldId) => {
// 对路由变化做出响应...
})
</script>
也可以使用beforeRouteUpdate
导航守卫
<script setup>
import { onBeforeRouteUpdate } from 'vue-router'
// ...
onBeforeRouteUpdate(async (to, from) => {
// 对路由变化做出响应...
userData.value = await fetchUser(to.params.id)
})
</script>
2.捕获所有路由或 404 Not found 路由
常规参数只匹配url
片段之间的字符,用/
分隔。如果我们想匹配任意路径,我们可以使用自定义的路径参数正则表达式,在路径参数后面的括号中加入 正则表达式
const routes = [
// 将匹配所有内容并将其放在 `route.params.pathMatch` 下
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
// 将匹配以 `/user-` 开头的所有内容,并将其放在 `route.params.afterUser` 下
{ path: '/user-:afterUser(.*)', component: UserGeneric },
]
注意: 这种匹配方式的优先级最低,优先级高低为 /test
> /test/:id
> /test:afterUser(.*)*
> /:pathMatch(.*)*
,就一句话,越精准的匹配上,优先级越高
二、路由的匹配语法
路由的匹配可以通过正则来更加灵活、精确的匹配
const routes = [
// /:orderId -> 仅匹配数字
{ path: '/:orderId(\\d+)' },
// /:productName -> 匹配其他任何内容
{ path: '/:productName' },
]
const routes = [
// /:chapters -> 匹配 /one, /one/two, /one/two/three, 等
{ path: '/:chapters+' },
// /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
{ path: '/:chapters*' },
]
这个具体就看官网了,难得写了
1.Sensitive 与 strict 路由配置
默认情况下,所有路由是不区分大小写的,并且能匹配带有或不带有尾部斜线的路由。例如,路由/users
将匹配 /users
、/users/
、甚至 /Users/
。这种行为可以通过strict
和sensitive
选项来修改,它们既可以应用在整个全局路由上,又可以应用于当前路由上:
const router = createRouter({
history: createWebHistory(),
routes: [
// 将匹配 /users/posva 而非:
// - /users/posva/ 当 strict: true
// - /Users/posva 当 sensitive: true
{ path: '/users/:id', sensitive: true },
// 将匹配 /users, /Users, 以及 /users/42 而非 /users/ 或 /users/42/
{ path: '/users/:id?' },
],
strict: true, // 严格匹配,不允许带有尾部斜线
sensitive: true, //区分大小写
})
三、嵌套路由
在子路由中的path加/
和不加有很大的区别,以 /
开头的嵌套路径将被视为根路径
{
path: "/test",
name: "test",
hidden: false,
component: () => import("@/pages/test/test.vue"),
children: [
{
//path: "center", //path不加 / ,将匹配到http://localhost:9527/#/test/center
path: "/center", //path加 /,将匹配到http://localhost:9527/#/center
name: "center",
component: () => import("@/pages/center/center.vue"),
},
],
},
1.忽略父组件(4.1+版本)
const routes = [
{
path: "/test",
name: "test",
hidden: false,
// component: () => import("@/pages/test/test.vue"),
children: [
{
path: "center",
name: "center",
component: () => import("@/pages/center/center.vue"),
},
],
},
]
由于父级没有指定路由组件,*<router-view>
将跳过父级并仅使用子路由组件。
{
path: "/test",
name: "test",
hidden: false,
component: () => import("@/pages/test/test.vue"),
children: [
{
path: "center",
name: "center",
component: () => import("@/pages/center/center.vue"),
},
],
},
四、命名路由
当创建一个路由时,可以选择给路由一个name
:
const routes = [
{
path: '/user/:username',
name: 'user',
component: User
}
]
这个路由name在整个路由中是唯一的,可以通过router.push({ name: 'user', params: { username: 'erina' } })
这种方式来进行路由跳转,将创建一个指向 /user/erina
的链接。可以通过route.params
来获取参数,使用name
有很多优点:
- 没有硬编码的 URL
-
params
的自动编码/解码 - 防止你在 URL 中出现打字错误
- 不用关心路由嵌套,就算是100层也能直接使用
name
来直接定位
命名路由通过params
来传递参数,一定要使用动态路由,这样才能获取到传递的参数
### 五、命名视图
有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果`router-view`没有设置名字,那么默认为 default
```html
<router-view class="view left-sidebar" name="LeftSidebar" />
<router-view class="view main-content" />
<router-view class="view right-sidebar" name="RightSidebar" />
const router = createRouter({
history: createWebHashHistory(),
routes: [
{
path: '/',
components: {
default: Home,
// LeftSidebar: LeftSidebar 的缩写
LeftSidebar,
// 它们与 `<router-view>` 上的 `name` 属性匹配
RightSidebar,
},
},
],
})
六、别名
重定向是指当用户访问 /home 时,URL 会被 / 替换,然后匹配成 /。那么什么是别名呢?
将/
别名为/home
,意味着当用户访问/home
时,URL 仍然是 /home
,但会被渲染/
的内容。
const routes = [
{
path: '/users',
component: UsersLayout,
children: [
// 为这 3 个 URL 呈现 UserList
// - /users
// - /users/list
// - /people
{ path: '', component: UserList, alias: ['/people', 'list'] },
],
},
]
如果路由有参数,请确保在任何绝对别名中包含它们:
const routes = [
{
path: '/users/:id',
component: UsersByIdLayout,
children: [
// 为这 3 个 URL 呈现 UserDetails
// - /users/24
// - /users/24/profile
// - /24
{ path: 'profile', component: UserDetails, alias: ['/:id', ''] },
],
},
]
注意 如果别名和另一个路由path相同,谁在前面谁显示
const routes = [
{
path: "/homePage",
name: "homePage",
alias: "/webgpu", //就算下面有/webgpu的路由,一样会渲染/homePage的内容
component: () => import("@/pages/home/children/homePage.vue"),
},
{
path: "/webgpu",
name: "webgpu",
component: () => import("@/pages/home/children/webgpu.vue"),
},
]
七、路由组件传参
当props
设置为true
时,route.params
将被设置为组件的props
。
const routes = [
{ path: '/user/:id', component: User, props: true }
]
路由为/user/123456
<!-- User.vue -->
<script setup>
defineProps({
id: String //值为123456
})
</script>
<template>
<div>
User {{ id }}
</div>
</template>
当props
是一个对象
时,它将原样设置为组件props
。当 props 是静态的时候很有用。
const routes = [
{ path: '/user/:id', component: User, props: {username:"admin"}}
]
可以创建一个返回props
的函数。这允许你将参数转换为其他类型,将静态值与基于路由的值相结合等等。
const routes = [
{
path: '/search',
component: SearchUser,
props: route => ({ abc: "13456" })
}
]
全局传参
<RouterView v-slot="{ Component }">
<component
:is="Component"
view-prop="value"
/>
</RouterView>
这样每一个路由组件都能获取到view-prop
参数
八、路由守卫
1. 全局路由前置守卫
const router = createRouter({ ... })
router.beforeEach((to, from) => {
//通过一个路由地址重定向到一个不同的地址,如同调用 router.push(),且可以传入诸如 replace: true 或 name: 'home' 之类的选项。它会中断当前的导航,同时用相同的 from 创建一个新导航。
// return { name: 'Login' }
//返回true或者返回undefine来调用下一个守卫
//return true
// 返回 false 以取消导航
return false
})
如果遇到了意料之外的情况,可能会抛出一个Error
。这会取消导航并且调用router.onError()
注册过的回调
2. 全局解析守卫
你可以用router.beforeResolve
注册一个全局守卫。这和router.beforeEach
类似,因为它在每次导航时都会触发,不同的是,解析守卫刚好会在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用
router.beforeResolve((to,from) =>{
console.log(to,from,"beforeResolve");
});
3. 在守卫内的全局注入
从 Vue 3.3 开始,你可以在导航守卫内使用inject()
方法
// main.ts
const app = createApp(App)
app.provide('global', 'hello injections')
// router.ts or main.ts
router.beforeEach((to, from) => {
const global = inject('global') // 'hello injections'
// a pinia store
const userStore = useAuthStore()
// ...
})
4. 路由独享的守卫
beforeEnter
守卫 只在进入路由时触发,不会在 params、query 或 hash 改变时触发。例如,从 /users/2
进入到 /users/3
或者从 /users/2#info
进入到 /users/2#projects
。它们只有在 从一个不同的 路由导航时,才会被触发。
function removeQueryParams(to) {
if (Object.keys(to.query).length)
return { path: to.path, query: {}, hash: to.hash }
}
function removeHash(to) {
if (to.hash) return { path: to.path, query: to.query, hash: '' }
}
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: [removeQueryParams, removeHash],
},
{
path: '/about',
component: UserDetails,
beforeEnter: [removeQueryParams],
},
]
当配合嵌套路由使用时,父路由和子路由都可以使用beforeEnter
。如果放在父级路由上,路由在具有相同父级的子路由之间移动时,它不会被触发。例如:
const routes = [
{
path: '/user',
beforeEnter() {
// ...
},
children: [
{ path: 'list', component: UserList },
{ path: 'details', component: UserDetails },
],
},
]
示例中的 beforeEnter 在 /user/list
和 /user/details
之间移动时不会被调用,因为它们共享相同的父级路由。如果我们直接将 beforeEnter 守卫放在details
路由上,那么在这两个路由之间移动时就会被调用。
5. 完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用 beforeRouteLeave 守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫(2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
九、RouterView 插槽
RotuerView 组件暴露了一个插槽,可以用来渲染路由组件:
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" />
</keep-alive>
</router-view>
十、滚动行为
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。
注意: 这个功能只在支持 history.pushState 的浏览器中可用。
const router = createRouter({
history: createWebHashHistory(),
routes: [...],
scrollBehavior (to, from, savedPosition) {
return {
top:10,
behavior: 'smooth', //让滚动更平滑,不是直接跳到这个位置,而是慢慢滚动到这个位置
}
// return 期望滚动到哪个的位置
}
})
也可以通过 el 传递一个 CSS 选择器或一个 DOM 元素。在这种情况下,top 和 left 将被视为该元素的相对偏移量
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
// 始终在元素 #main 上方滚动 10px
return {
// 也可以这么写
// el: document.getElementById('main'),
el: '#main',
// 在元素上 10 像素
top: 10,
}
},
})
十一、导航故障
当使用router-link
组件时,Vue Router
会自动调用router.push
来触发一次导航。虽然大多数链接的预期行为是将用户导航到一个新页面,但也有少数情况下用户将留在同一页面上:
- 用户已经位于他们正在尝试导航到的页面
- 一个导航守卫通过调用
return false
中断了这次导航 - 当前的导航守卫还没有完成时,一个新的导航守卫会出现了
- 一个导航守卫通过返回一个新的位置,重定向到其他地方 (例如,return ‘/login’)
- 一个导航守卫抛出了一个
Error
注意:导航是异步的
如果导航被阻止,导致用户停留在同一个页面上,由router.push
返回的Promise
的解析值将是Navigation Failure
。否则,它将是一个falsy
值(通常是undefined
)。这样我们就可以区分我们导航是否离开了当前位置:
const navigationResult = await router.push('/my-profile')
if (navigationResult) {
// 导航被阻止
} else {
// 导航成功 (包括重新导航的情况)
this.isMenuOpen = false
}
Navigation Failure 是带有一些额外属性的Error
实例,这些属性为我们提供了足够的信息,让我们知道哪些导航被阻止了以及为什么被阻止了。要检查导航结果的性质,请使用isNavigationFailure
函数:
import { NavigationFailureType, isNavigationFailure } from 'vue-router'
// 试图离开未保存的编辑文本界面
const failure = await router.push('/articles/2')
if (isNavigationFailure(failure, NavigationFailureType.aborted)) {
// 给用户显示一个小通知
showToast('You have unsaved changes, discard and leave anyway?')
}
NavigationFailureType
有三种值
-
aborted
:在导航守卫中返回false
中断了本次导航。 -
cancelled
: 在当前导航完成之前又有了一个新的导航。比如,在等待导航守卫的过程中又调用了router.push
。 -
duplicated
:导航被阻止,因为我们已经在目标位置了。
十二、动态路由
router.addRoute({ name: 'admin', path: '/admin', component: Admin })
router.addRoute('admin', { path: 'settings', compon