Vue Router
路由匹配语法
-
在参数中自定义正则
-
根据参数内容区分两个会匹配完全相同URL的路由
const routes = [ // /:orderId -> 仅匹配数字 { path: '/:orderId(\\d+)' }, // /:productName -> 匹配其他任何内容 { path: '/:productName' }, ] // /25 将匹配 /:orderId,其他情况将会匹配 /:productName
-
-
可重复的参数
-
*(0个或多个)、+(1个或多个)
const routes = [ // /:chapters -> 匹配 /one, /one/two, /one/two/three, 等 { path: '/:chapters+' }, // /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等 { path: '/:chapters*' }, ]
-
-
Sensitive 与 strict 路由配置
-
默认情况下,所有路由是不区分大小写的,并且能匹配带有或不带有尾部斜线的路由
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, // applies to all routes })
-
-
可选参数
-
通过使用
?
修饰符(0 个或 1 个)将一个参数标记为可选 -
注意
-
*
在技术上也标志着一个参数是可选的,但?
参数不能重复const routes = [ // 匹配 /users 和 /users/posva { path: '/users/:userId?' }, // 匹配 /users 和 /users/42 { path: '/users/:userId(\\d+)?' }, ]
-
-
重定向和别名
-
重定向的目标
-
/
const routes = [{ path: '/home', redirect: '/' }]
-
命名路由
const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
-
动态返回重定向目标
const routes = [ { // /search/screens -> /search?q=screens path: '/search/:searchText', redirect: to => { // 方法接收目标路由作为参数 // return 重定向的字符串路径/路径对象 return { path: '/search', query: { q: to.params.searchText } } }, }, { path: '/search', // ... }, ]
-
-
相对重定向(重定向到相对位置)
const routes = [ { // 将总是把/users/123/posts重定向到/users/123/profile。 path: '/users/:id/posts', redirect: to => { // 该函数接收目标路由作为参数 return to.path.replace(/posts$/, 'profile') }, }, ]
-
别名
-
自由地将 UI 结构映射到一个任意的 URL,而不受配置的嵌套结构的限制
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', ''] }, ], }, ]
-
导航守卫
-
全局前置路由
-
false
: 取消当前的导航。如果浏览器的 URL 改变了,那么 URL 地址会重置到from
路由对应的地址。 -
一个路由地址: 通过一个路由地址重定向到一个不同的地址
router.beforeEach(async (to, from) => { if ( // 检查用户是否已登录 !isAuthenticated && // ❗️ 避免无限重定向 to.name !== 'Login' ) { // 将用户重定向到登录页面 return { name: 'Login' } } })
-
可选的第三个参数next
router.beforeEach((to, from, next) => { if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' }) else next() })
-
-
全局解析路由
-
解析守卫刚好会在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用
-
例子:根据路由在元信息属性确保用户访问摄像头的权限
router.beforeResolve(async to => { if (to.meta.requiresCamera) { try { await askForCameraPermission() } catch (error) { if (error instanceof NotAllowedError) { // ... 处理错误,然后取消导航 return false } else { // 意料之外的错误,取消导航并把错误传给全局处理器 throw error } } } })
-
-
全局后置钩子
-
对于分析、更改页面标题、声明页面等辅助功能很有用
router.afterEach((to, from, failure) => { if (!failure) sendToAnalytics(to.fullPath) })
-
-
路由元信息
-
接收属性对象的
meta
属性可以在路由地址和导航守卫上都被访问到const routes = [ { path: '/posts', component: PostsLayout, children: [ { path: 'new', component: PostsNew, // 只有经过身份验证的用户才能创建帖子 meta: { requiresAuth: true }, }, { path: ':id', component: PostsDetail // 任何人都可以阅读文章 meta: { requiresAuth: false }, } ] } ] router.beforeEach((to, from) => { // 而不是去检查每条路由记录 // to.matched.some(record => record.meta.requiresAuth) if (to.meta.requiresAuth && !auth.isLoggedIn()) { // 此路由需要授权,请检查是否已登录 // 如果没有,则重定向到登录页面 return { path: '/login', // 保存我们所在的位置,以便以后再来 query: { redirect: to.fullPath }, } } })
-
-
数据获取
-
导航完成后获取数据
<template> <div class="post"> <div v-if="loading" class="loading">Loading...</div> <div v-if="error" class="error">{{ error }}</div> <div v-if="post" class="content"> <h2>{{ post.title }}</h2> <p>{{ post.body }}</p> </div> </div> </template> <script setup> import { ref, watch } from 'vue' import { useRoute } from 'vue-router' import { getPost } from './api.js' const route = useRoute() const loading = ref(false) const post = ref(null) const error = ref(null) // 侦听路由的参数,以便再次获取数据 watch(() => route.params.id, fetchData, { immediate: true }) async function fetchData(id) { error.value = post.value = null loading.value = true try { // 用获取数据的工具函数 / API 包裹器替换 `getPost` post.value = await getPost(id) } catch (err) { error.value = err.toString() } finally { loading.value = false } } </script>
-
导航前获取数据
export default { data() { return { post: null, error: null, } }, beforeRouteEnter(to, from, next) { try { const post = await getPost(to.params.id) // `setPost` 方法定义在下面的代码中 next(vm => vm.setPost(post)) } catch (err) { // `setError` 方法定义在下面的代码中 next(vm => vm.setError(err)) } }, // 路由改变前,组件就已经渲染完了 // 逻辑稍稍不同 async beforeRouteUpdate(to, from) { this.post = null getPost(to.params.id).then(this.setPost).catch(this.setError) }, methods: { setPost(post) { this.post = post }, setError(err) { this.error = err.toString() } } }
-
-
RouterView插槽
-
KeepAlive & Transition
-
当在处理 KeepAlive 组件时,通常想要保持路由组件活跃,而不是 RouterView 本身,可以将 KeepAlive 组件放置在插槽内
-
可以在 Transition 组件内使用 KeepAlive 组件
<router-view v-slot="{ Component }"> <transition> <keep-alive> <component :is="Component" /> </keep-alive> </transition> </router-view>
-
-
模板引用
-
使用插槽可以让我们直接将模板引用放置在路由组件上
<router-view v-slot="{ Component }"> <component :is="Component" ref="mainContent" /> </router-view>
-
-
-
滚动行为
-
自定义路由切换时页面如何滚动
-
注意:这个功能只在支持 history.pushState 的浏览器中可用(history.pushState是 HTML5 History API 的一个方法,允许开发者通过 JavaScript 动态修改浏览器的地址栏 URL,而无需重新加载页面)
-
传递一个 CSS 选择器或一个 DOM 元素
const router = createRouter({ scrollBehavior(to, from, savedPosition) { // 始终在元素 #main 上方滚动 10px return { // 也可以这么写 // el: document.getElementById('main'), el: '#main', // 在元素上 10 像素 top: 10, } }, })
-
返回一个 falsy 的值,或者是一个空对象,那么不会发生滚动
-
返回
savedPosition
,在按下 后退/前进 按钮时,就会像浏览器的原生表现那样const router = createRouter({ scrollBehavior(to, from, savedPosition) { if (savedPosition) { return savedPosition } else { return { top: 0 } } }, })
-
模拟 “滚动到锚点” 的行为
const router = createRouter({ scrollBehavior(to, from, savedPosition) { if (to.hash) { return { el: to.hash, } } }, })
-
延迟滚动
const router = createRouter({ scrollBehavior(to, from, savedPosition) { return new Promise((resolve, reject) => { setTimeout(() => { resolve({ left: 0, top: 0 }) }, 500) }) }, })
-
-
路由懒加载
-
动态导入
const router = createRouter({ // ... routes: [ { path: '/users/:id', component: UserDetails } // 或在路由定义里直接使用它 { path: '/users/:id', component: () => import('./views/UserDetails.vue') }, ], })
-
把组件按组分块
-
webpack
const UserDetails = () => import(/* webpackChunkName: "group-user" */ './UserDetails.vue') const UserDashboard = () => import(/* webpackChunkName: "group-user" */ './UserDashboard.vue') const UserProfileEdit = () => import(/* webpackChunkName: "group-user" */ './UserProfileEdit.vue')
-
vite
// vite.config.js export default defineConfig({ build: { rollupOptions: { // https://rollupjs.org/guide/en/#outputmanualchunks output: { manualChunks: { 'group-user': [ './src/UserDetails', './src/UserDashboard', './src/UserProfileEdit', ], }, }, }, }, })
-
-
Router API
TS接口
Router
-
属性
-
currentRoute(只读)
- fullPath:包括
search
和hash
在内的完整地址 - hash:当前地址的 hash。如果存在则以
#
开头 - matched:数组,只包含直接的组件,不包含重定向的记录
- meta:从所有匹配的路由记录中合并的
meta
属性 - name:匹配的路由名称
- params:从
path
中提取出来并解码后的参数对象 - path:经过百分号编码的 URL 中的 pathname 段
- query:代表当前地址的
search
属性的对象 - rediredtedForm:包含在重定向到当前地址之前,我们最初想访问的地址
- fullPath:包括
-
listening:boolean
- 允许关闭历史事件的监听器
-
options(只读)
-
创建路由器时的原始选项对象
-
end:其 RegExp 是否应该在末尾加一个
$
以匹配到末尾 -
history:路由器使用的历史记录模式
-
linkActiveClass:匹配当前路由的 RouterLink 默认的 CSS class
-
linkExactActiveClass:严格匹配当前路由的 RouterLink 默认的 CSS class
-
rotues(只读):应该添加到路由器的初始路由列表
-
scrollBehavior:当在页面之间导航时控制滚动的功能。可以返回一个 Promise 来延迟滚动
function scrollBehavior(to, from, savedPosition) { // `to` 和 `from` 都是路由路径 // `savedPosition` 如果不存在可以为 null }
-
sensitive:使该 RegExp 区分大小写
-
strict:是否禁止尾部斜线
-
stringifyQuery:对查询对象进行字符串化的自定义实现
-
parseQuery:解析查询的自定义实现
-
-
-
方法
-
addRoute(
parentName
,route
): () =>void
- 添加一个新的路由记录,将其作为一个已有路由的子路由
- parentName:route 应该被加入到的父级路由记录
- route:要加入的路由记录
-
afterEach(
guard
): () =>void
-
添加一个导航钩子,它会在每次导航之后被执行。返回一个用来移除该钩子的函数
-
guard:要加入的导航钩子
router.afterEach((to, from, failure) => { if (isNavigationFailure(failure)) { console.log('failed navigation', failure) } })
-
-
back():
void
-
通过调用
history.back()
在可能的情况下在历史中后退。相当于router.go(-1)
-
beforeEach(
guard
): () =>void
-
添加一个导航钩子,它会在每次导航之前被执行。返回一个用来移除该钩子的函数。
-
guard:要加入的导航钩子
-
beforeResolve(
guard
): () =>void
-
添加一个导航守卫,它会在导航将要被解析之前被执行。此时所有组件都已经获取完毕,且其它导航守卫也都已经完成调用。返回一个用来移除该守卫的函数
router.beforeResolve(to => { if (to.meta.requiresAuth && !isAuthenticated) return false })
-
forward():
void
- 通过调用
history.forward()
在可能的情况下在历史中前进。相当于router.go(1)
。
- 通过调用
-
getRoutes():[]
-
获得所有路由记录的完整列表
-
go(
delta
):void
- 允许你在历史中前进或后退。相当于
router.go()
- 允许你在历史中前进或后退。相当于
-
hasRoute(
name
):boolean
- 检查一个给定名称的路由是否存在
-
isReady():
Promise
<void
> -
返回一个 Promise,它会在路由器完成初始导航之后被解析,也就是说这时所有和初始路由有关联的异步入口钩子和异步组件都已经被解析。如果初始导航已经发生,则该 Promise 会被立刻解析。
-
这在服务端渲染中确认服务端和客户端输出一致的时候非常有用。注意在服务端需要手动加入初始地址,而在客户端,路由器会从 URL 中自动获取。
-
onError(
handler
): () =>void
-
添加一个错误处理器,它会在每次导航遇到未被捕获的错误出现时被调用。其中包括同步和异步被抛出的错误、在任何导航守卫中返回或传入
next
的错误、尝试解析一个需要渲染路由的异步组件时发生的错误。 -
handler:要注册的错误处理器
-
push(
to
):Promise
<undefined
|void
|NavigationFailure
>- 程序式地通过将一条记录加入到历史栈中来导航到一个新的 URL
- to:要导航到的路由
-
removeRoute(
name
):void
- 根据其名称移除一个现有的路由
-
replace(
to
):Promise
<undefined
|void
|NavigationFailure
>- 程序式地通过替换历史栈中的当前记录来导航到一个新的 URL
-
resolve(
to
,currentLocation?
):RouteLocation
& {href
:string
}- 返回一个路由地址的规范化版本。同时包含一个包含任何现有 base 的 href 属性
-
to:要解析的原始路由地址
-
currentLocation?:可选的被解析的当前地址
-
变量
-
RouterLink
- 用来渲染一个链接的组件,该链接在被点击时会触发导航
-
RouterView
- 用于显示用户当前所处路由的组件
-
START_LOCATION
-
路由器的初始路由位置。可以在导航守卫中使用来区分初始导航
import { START_LOCATION } from 'vue-router' router.beforeEach((to, from) => { if (from === START_LOCATION) { // 初始导航 } })
-
函数
- createMemoryHistory(
base?
):RouterHistory
- 创建一个基于内存的历史。该历史的主要目的是为了处理服务端渲染。它从一个不存在的特殊位置开始
- 用户可以通过调用
router.push
或router.replace
将该位置替换成起始位置
-
createRouter(
options
):Router
- 创建一个可以被 Vue 应用使用的 Router 实例
-
createWebHashHistory(
base?
):RouterHistory
- 创建一个 hash 模式的历史
-
createWebHistory(
base?
):RouterHistory
- 创建一个 HTML5 历史
-
isNavigationFailure(
error
,type?
): error is NavigationRedirectError-
检查一个对象是否是 NavigationFailure
import { isNavigationFailure, NavigationFailureType } from 'vue-router' router.afterEach((to, from, failure) => { // 任何类型的导航失败 if (isNavigationFailure(failure)) { // ... } // 重复的导航 if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { // ... } // 中止或取消的导航 if (isNavigationFailure(failure, NavigationFailureType.aborted | NavigationFailureType.canceled)) { // ... } })
-
-
loadRouteLocation(
route
):Promise
- 确保路由被加载,所以它可以作为一个 prop 传递给
<RouterView>
- 确保路由被加载,所以它可以作为一个 prop 传递给
-
onBeforeRouteLeave(
leaveGuard
):void
- 添加一个导航守卫,不论当前位置的组件何时离开都会触发。类似于 beforeRouteLeave,但可以在任意组件中使用。当组件被卸载时,该守卫会被移除
-
onBeforeRouteUpdate(
updateGuard
):void
- 添加一个导航守卫,不论当前位置何时被更新都会触发。类似于 beforeRouteUpdate,但可以在任何组件中使用。当组件被卸载时,该守卫会被移除
-
useLink(
props
):Object
-
useRoute():
RouteLocationNormalizedLoaded
- 返回当前的路由地址
- useRouter():
Router
- 返回路由器实例