Open wbccb opened 1 year ago
本文基于vue-router 4.1.6版本
在解析Vue Router的源码时发现一些特殊的路由匹配解析规则,在观看官方文档时,发现很多没用过的新奇特性,因此编写本文作为Vue Router官方文档的总结,以便后面开发能够快速借助此文章找到想要的东西
Vue Router
使用自定义组件 router-link 来创建链接,使得 Vue Router 可以在不重新加载页面的情况下更改 URL,处理 URL 的生成以及编码。
router-link
URL
router-view 将显示与 url 对应的组件。你可以把它放在任何地方,以适应你的布局
router-view
url
Vue Router官网提供的路径调试工具:https://paths.esm.dev/?p=AAMeJSyAwR4UbFDAFxAcAGAIJXMAAA
const routes = [ { path: '/', component: Home }, { path: '/about', component: About }, ]
const routes = [ // 动态字段以冒号开始 { path: '/users/:id', component: User }, ]
现在像 /users/johnny 和 /users/jolyne 这样的 URL 都会映射到同一个路由。
/users/johnny
/users/jolyne
路径参数 用冒号 : 表示。当一个路由被匹配时,它的 params 的值将在每个组件中以 this.$route.params 的形式暴露出来
:
this.$route.params
常规参数只匹配 url 片段之间的字符,用 / 分隔。如果我们想匹配任意路径,我们可以使用自定义的 路径参数 正则表达式,在 路径参数 后面的括号中加入 正则表达式 :
const routes = [ // 将匹配所有内容并将其放在 `$route.params.pathMatch` 下 { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound } ]
const routes = [ // 将匹配以 `/user-` 开头的所有内容,并将其放在 `$route.params.afterUser` 下 { path: '/user-:afterUser(.*)', component: UserGeneric } ]
const routes = [ // /:orderId -> 仅匹配数字 { path: '/:orderId(\\d+)' }, // /:productName -> 匹配其他任何内容 { path: '/:productName' }, ]
现在,转到 /25 将匹配 /:orderId,其他情况将会匹配 /:productName。routes 数组的顺序并不重要!
如果你需要匹配具有多个部分的路由,如 /first/second/third,你应该用 *(0 个或多个)和 +(1 个或多个)将参数标记为可重复:
const routes = [ // /:chapters -> 匹配 /one, /one/two, /one/two/three, 等 { path: '/:chapters+' }, // /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等 { path: '/:chapters*' }, ]
当我们使用上面这种写法时,我们通过this.$router.params拿到的数据就是数组了!
this.$router.params
const routes = [ // 仅匹配数字 // 匹配 /1, /1/2, 等 { path: '/:chapters(\\d+)+' }, // 匹配 /, /1, /1/2, 等 { path: '/:chapters(\\d+)*' }, ]
你也可以通过使用 ? 修饰符(0 个或 1 个)将一个参数标记为可选:
const routes = [ // 匹配 /users 和 /users/posva { path: '/users/:userId?' }, // 匹配 /users 和 /users/42 { path: '/users/:userId(\\d+)?' }, ]
请注意,* 在技术上也标志着一个参数是可选的,但 ? 参数不能重复。 路由sensitive和strict变量
请注意,* 在技术上也标志着一个参数是可选的,但 ? 参数不能重复。
默认情况下,所有路由是不区分大小写的,并且能匹配带有或不带有尾部斜线的路由。例如,路由 /users 将匹配 /users、/users/、甚至 /Users/
当使用strict: true时,不能匹配尾部斜线的路由
strict: true
默认为false,意味着默认情况下,路由 /users 同时匹配 /users 和 /users/
false
const router = createRouter({ history: createWebHistory(), routes: [ // 将匹配 /users, /Users, 以及 /users/42 而非 /users/ 或 /users/42/ { path: '/users/:id?' }, ], strict: true, // applies to all routes })
当使用sensitive: true时,在乎大小写
sensitive: true
默认为false,意味着默认情况下,路由 /users 同时匹配 /users 和 /Users 注意可以单独应用于一个路由 const router = createRouter({ history: createWebHistory(), routes: [ // 将匹配 /users/posva 而非:/Users/posva { path: '/users/:id', sensitive: true }, // 将匹配 /users, /Users, 以及 /users/42 而非 /users/ 或 /users/42/ { path: '/users/:id?' }, ], strict: true, // applies to all routes }) 嵌套路由(子路由) const routes = [ { path: '/user/:id', component: User, children: [ { // 当 /user/:id/profile 匹配成功 // UserProfile 将被渲染到 User 的 <router-view> 内部 path: 'profile', component: UserProfile, }, { // 当 /user/:id/posts 匹配成功 // UserPosts 将被渲染到 User 的 <router-view> 内部 path: 'posts', component: UserPosts, }, ], }, ]
默认为false,意味着默认情况下,路由 /users 同时匹配 /users 和 /Users
注意可以单独应用于一个路由
const router = createRouter({ history: createWebHistory(), routes: [ // 将匹配 /users/posva 而非:/Users/posva { path: '/users/:id', sensitive: true }, // 将匹配 /users, /Users, 以及 /users/42 而非 /users/ 或 /users/42/ { path: '/users/:id?' }, ], strict: true, // applies to all routes })
const routes = [ { path: '/user/:id', component: User, children: [ { // 当 /user/:id/profile 匹配成功 // UserProfile 将被渲染到 User 的 <router-view> 内部 path: 'profile', component: UserProfile, }, { // 当 /user/:id/posts 匹配成功 // UserPosts 将被渲染到 User 的 <router-view> 内部 path: 'posts', component: UserPosts, }, ], }, ]
当你访问 /user/eduardo 时,在 User 的 router-view 里面什么都不会呈现,因为没有匹配到嵌套路由。也许你确实想在那里渲染一些东西。在这种情况下,你可以提供一个空的嵌套路径
/user/eduardo
User
即你匹配到父路由的路径时,想要一个默认子路由,可以使用一个空的嵌套路径 const routes = [ { path: '/user/:id', component: User, children: [ // 当 /user/:id 匹配成功 // UserHome 将被渲染到 User 的 <router-view> 内部 { path: '', component: UserHome },
即你匹配到父路由的路径时,想要一个默认子路由,可以使用一个空的嵌套路径
const routes = [ { path: '/user/:id', component: User, children: [ // 当 /user/:id 匹配成功 // UserHome 将被渲染到 User 的 <router-view> 内部 { path: '', component: UserHome },
// ...其他子路由 ],
}, ]
在一些场景中,你可能希望导航到命名路由而不导航到嵌套路由。例如,你想导航 /user/:id 而不显示嵌套路由。那样的话,你还可以命名父路由,但请注意重新加载页面将始终显示嵌套的子路由,因为它被视为指向路径/users/:id 的导航,而不是命名路由: ```javascript const routes = [ { path: '/user/:id', name: 'user-parent', component: User, children: [{ path: '', name: 'user', component: UserHome }], }, ]
<router-link :to="...">
router.push(...)
// 字符串路径 router.push('/users/eduardo') // 带有路径的对象 router.push({ path: '/users/eduardo' }) // 命名的路由,并加上参数,让路由建立 url router.push({ name: 'user', params: { username: 'eduardo' } }) // 带查询参数,结果是 /register?plan=private router.push({ path: '/register', query: { plan: 'private' } }) // 带 hash,结果是 /about#team router.push({ path: '/about', hash: '#team' })
注意:如果提供了 path,params 会被忽略,一般是name+params(代表动态匹配那个参数名),或者直接手写路径path const username = "eduardo"; router.push({ path: `/user/${username}` }) // -> /user/eduardo // 如果可能的话,使用 `name` 和 `params` 从自动 URL 编码中获益 router.push({ name: 'user', params: { username } }) // -> /user/eduardo 命名视图 有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default。 意味着我们一个xxx.vue文件中可以使用多个<router-view/>,然后使用多个路由配置适应它 <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>
注意:如果提供了 path,params 会被忽略,一般是name+params(代表动态匹配那个参数名),或者直接手写路径path
const username = "eduardo"; router.push({ path: `/user/${username}` }) // -> /user/eduardo // 如果可能的话,使用 `name` 和 `params` 从自动 URL 编码中获益 router.push({ name: 'user', params: { username } }) // -> /user/eduardo
有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default。 意味着我们一个xxx.vue文件中可以使用多个<router-view/>,然后使用多个路由配置适应它
xxx.vue
<router-view/>
<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>
比如我们可以使用下面的router
router
path: 'emails'
<router-view class="view main-content"></router-view>
path: 'profile'
name
const router = { path: '/settings', // 你也可以在顶级路由就配置命名视图 component: UserSettings, children: [ { path: 'emails', component: UserEmailsSubscriptions }, { path: 'profile', components: { LeftSidebar: LeftSidebar, RightSidebar: RightSidebar, default: MiddleConent } } ] }
// 重定向的目标可以是一个路径 const routes = [{ path: '/home', redirect: '/' }] // 重定向的目标也可以是一个命名的路由 const routes = [{ path: '/home', redirect: { name: 'homepage' } }] // 可以使用function,动态返回重定向目标 const routes = [ { // /search/screens -> /search?q=screens path: '/search/:searchText', redirect: to => { // 方法接收目标路由作为参数 // return 重定向的字符串路径/路径对象 return { path: '/search', query: { q: to.params.searchText } } }, } ] // 可以使用function,重定向到相对位置 const routes = [ { // 将总是把/users/123/posts重定向到/users/123/profile。 path: '/users/:id/posts', redirect: to => { // 该函数接收目标路由作为参数 // 相对位置不以`/`开头 // 或 { path: 'profile'} return 'profile' }, }, ]
类似重定向的一种模式 比如,将 / 别名为 /home,意味着当用户访问 /home 时,URL 仍然是 /home,但会被匹配为用户正在访问 /
/
/home
const routes = [{ path: '/', component: Homepage, alias: '/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', ''] }, ], }, ]
注意要routes声明props: true才能自动转化
routes
props: true
const User = { template: '<div>User {{ $route.params.id }}</div>' } const routes = [{ path: '/user/:id', component: User }] // 上面可以替换成为下面这段代码 const User = { // 请确保添加一个与路由参数完全相同的 prop 名 props: ['id'], template: '<div>User {{ id }}</div>' } const routes = [{ path: '/user/:id', component: User, props: true }]
遇到命名视图时,要分开声明props: true
const routes = [ { path: '/user/:id', components: { default: User, sidebar: Sidebar }, props: { default: true, sidebar: false } } ]
props
当 props 是一个对象时,它将原样设置为组件 props。当 props 是静态的时候很有用
const routes = [ { path: '/promotion/from-newsletter', component: Promotion, props: { newsletterPopup: false } } ]
query
URL /search?q=vue 将传递 {query: 'vue'} 作为 props 传给 SearchUser 组件。
/search?q=vue
{query: 'vue'}
SearchUser
const routes = [ { path: '/search', component: SearchUser, props: route => ({ query: route.query.q }) } ]
注意next的正确调用
next
// BAD router.beforeEach((to, from, next) => { if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' }) // 如果用户未能验证身份,则 `next` 会被调用两次 next() }) // GOOD router.beforeEach((to, from, next) => { if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' }) else next() })
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中。 全局前置守卫是检测授权逻辑的理想位置
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中。
全局前置守卫是检测授权逻辑的理想位置
你可以使用 router.beforeEach 注册一个全局前置守卫:
const router = createRouter({ ... }) router.beforeEach((to, from) => { // ... // 返回 false 以取消导航 return false })
因为它在 每次导航时都会触发,但是确保在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用。 router.beforeResolve 是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置。 router.beforeResolve(async to => { if (to.meta.requiresCamera) { try { await askForCameraPermission() } catch (error) { if (error instanceof NotAllowedError) { // ... 处理错误,然后取消导航 return false } else { // 意料之外的错误,取消导航并把错误传给全局处理器 throw error } } } })
因为它在 每次导航时都会触发,但是确保在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用。
router.beforeResolve 是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置。
router.beforeResolve(async to => { if (to.meta.requiresCamera) { try { await askForCameraPermission() } catch (error) { if (error instanceof NotAllowedError) { // ... 处理错误,然后取消导航 return false } else { // 意料之外的错误,取消导航并把错误传给全局处理器 throw error } } } })
它们对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用。
这些钩子不会接受 next 函数也不会改变导航本身:
router.afterEach((to, from) => { sendToAnalytics(to.fullPath) })
它们也反映了 navigation failures 作为第三个参数:
router.afterEach((to, from, failure) => { if (!failure) sendToAnalytics(to.fullPath) })
beforeEnter 守卫 只在进入路由时触发,不会在 params、query 或 hash 改变时触发。例如,从 /users/2 进入到 /users/3 或者从 /users/2#info 进入到 /users/2#projects。它们只有在 从一个不同的 路由导航时,才会被触发。
const routes = [ { path: '/users/:id', component: UserDetails, beforeEnter: (to, from) => { // reject the navigation return false }, }, ]
const UserDetails = { template: `...`, beforeRouteEnter(to, from) { // 在渲染该组件的对应路由被验证前调用 // 不能获取组件实例 `this` ! // 因为当守卫执行时,组件实例还没被创建! }, beforeRouteUpdate(to, from) { // 在当前路由改变,但是该组件被复用时调用 // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候, // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this` }, beforeRouteLeave(to, from) { // 在导航离开渲染该组件的对应路由时调用 // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this` }, }
beforeRouteLeave: 通常用来预防用户在还未保存修改前突然离开。该导航可以通过返回 false 来取消
beforeRouteLeave
beforeRouteEnter (to, from, next) { next(vm => { // 通过 `vm` 访问组件实例 }) } beforeRouteUpdate (to, from) { // just use `this` this.name = to.params.name } beforeRouteLeave (to, from) { const answer = window.confirm('Do you really want to leave? you have unsaved changes!') if (!answer) return false }
组合式API写法
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router' import { ref } from 'vue' export default { setup() { // 与 beforeRouteLeave 相同,无法访问 `this` onBeforeRouteLeave((to, from) => { const answer = window.confirm( 'Do you really want to leave? you have unsaved changes!' ) // 取消导航并停留在同一页面上 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) } }) }, }
export default { data() { return { loading: false, post: null, error: null, } }, created() { // watch 路由的参数,以便再次获取数据 this.$watch( () => this.$route.params, () => { this.fetchData() }, // 组件创建完后获取数据, // 此时 data 已经被 observed 了 {immediate: true} ) } }
如果复用一个路由,比如/user/:id会导致不同的path会使用同一个路由,那么就不会调用beforeRouteEnter,因此我们需要在beforeRouteUpdate获取数据
/user/:id
path
beforeRouteEnter
beforeRouteUpdate
export default { data() { return { post: null, error: null, } }, beforeRouteEnter(to, from, next) { getPost(to.params.id, (err, post) => { next(vm => vm.setData(err, post)) }) }, // 路由改变前,组件就已经渲染完了 // 逻辑稍稍不同 async beforeRouteUpdate(to, from) { this.post = null try { this.post = await getPost(to.params.id) } catch (error) { this.error = error.toString() } }, }
route 对象是一个响应式对象,所以它的任何属性都可以被监听,但你应该避免监听整个 route 对象。在大多数情况下,你应该直接监听你期望改变的参数。 router具有push、replace等方法
push
replace
import { useRoute } from 'vue-router' import { ref, watch } from 'vue' export default { setup() { const route = useRoute() const userData = ref() // 当参数更改时获取用户信息 watch( () => route.params.id, async newId => { userData.value = await fetchUser(newId) } ) }, }
const routes = [ { path: '/custom-transition', component: PanelLeft, meta: { transition: 'slide-left' }, }, { path: '/other-transition', component: PanelRight, meta: { transition: 'slide-right' }, }, ]
<router-view v-slot="{ Component, route }"> <!-- 使用任何自定义过渡和回退到 `fade` --> <transition :name="route.meta.transition || 'fade'"> <component :is="Component" /> </transition> </router-view>
也可以使用afterEach进行meta字段的赋值
afterEach
meta
router.afterEach((to, from) => { const toDepth = to.path.split('/').length const fromDepth = from.path.split('/').length to.meta.transition = toDepth < fromDepth ? 'slide-right' : 'slide-left' })
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。
当创建一个 Router 实例,你可以提供一个 scrollBehavior 方法:
scrollBehavior 函数接收to和 from 路由对象,如 Navigation Guards。第三个参数 savedPosition,只有当这是一个 popstate 导航时才可用(由浏览器的后退/前进按钮触发)。
const router = createRouter({ history: createWebHashHistory(), routes: [...], scrollBehavior (to, from, savedPosition) { // return 期望滚动到哪个的位置 // 始终滚动到顶部 return { top: 0 } } })
const router = createRouter({ scrollBehavior(to, from, savedPosition) { // 始终在元素 #main 上方滚动 10px return { // 也可以这么写 // el: document.getElementById('main'), el: '#main', top: -10, } }, })
const router = createRouter({ scrollBehavior(to, from, savedPosition) { if (to.hash) { return { el: to.hash, behavior: 'smooth'//如果你的浏览器支持滚动行为,你可以让它变得更流畅 } } }, })
如果返回false,或者是一个空对象,那么不会发生滚动
返回 savedPosition,在按下 后退/前进 按钮时,就会像浏览器的原生表现那样:
const router = createRouter({ scrollBehavior(to, from, savedPosition) { if (savedPosition) { return savedPosition } else { return { top: 0 } } }, })
const router = createRouter({ scrollBehavior(to, from, savedPosition) { return new Promise((resolve, reject) => { setTimeout(() => { resolve({ left: 0, top: 0 }) }, 500) }) }, })
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中,你可以在rollupOptions下定义分块:
// vite.config.js export default defineConfig({ build: { rollupOptions: { // https://rollupjs.org/guide/en/#outputmanualchunks output: { manualChunks: { 'group-user': [ './src/UserDetails', './src/UserDashboard', './src/UserProfileEdit', ], }, }, }, }, })
const navigationResult = await router.push('/my-profile') if (navigationResult) { // 导航被阻止 } else { // 导航成功 (包括重新导航的情况) this.isMenuOpen = false }
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?') }
aborted
cancelled
router.push
duplicated
前言
在解析
Vue Router
的源码时发现一些特殊的路由匹配解析规则,在观看官方文档时,发现很多没用过的新奇特性,因此编写本文作为Vue Router
官方文档的总结,以便后面开发能够快速借助此文章找到想要的东西router-link和router-view
router-link
使用自定义组件
router-link
来创建链接,使得Vue Router
可以在不重新加载页面的情况下更改URL
,处理URL
的生成以及编码。router-view
router-view
将显示与url
对应的组件。你可以把它放在任何地方,以适应你的布局路由匹配规则
Vue Router
官网提供的路径调试工具:https://paths.esm.dev/?p=AAMeJSyAwR4UbFDAFxAcAGAIJXMAAA基础匹配
带参数的动态路由匹配
现在像
/users/johnny
和/users/jolyne
这样的 URL 都会映射到同一个路由。路径参数 用冒号
:
表示。当一个路由被匹配时,它的 params 的值将在每个组件中以this.$route.params
的形式暴露出来正则表达式路由
捕获所有路由或者404 Not found路由
常规参数只匹配 url 片段之间的字符,用 / 分隔。如果我们想匹配任意路径,我们可以使用自定义的 路径参数 正则表达式,在 路径参数 后面的括号中加入 正则表达式 :
匹配数字或者匹配其它内容的正则表达式
现在,转到 /25 将匹配 /:orderId,其他情况将会匹配 /:productName。routes 数组的顺序并不重要!
匹配多个子路由
如果你需要匹配具有多个部分的路由,如 /first/second/third,你应该用 *(0 个或多个)和 +(1 个或多个)将参数标记为可重复:
当我们使用上面这种写法时,我们通过
this.$router.params
拿到的数据就是数组了!数字限制+多个子路由一起使用
可选子路由
你也可以通过使用 ? 修饰符(0 个或 1 个)将一个参数标记为可选:
默认情况下,所有路由是不区分大小写的,并且能匹配带有或不带有尾部斜线的路由。例如,路由 /users 将匹配 /users、/users/、甚至 /Users/
当使用
strict: true
时,不能匹配尾部斜线的路由当使用
sensitive: true
时,在乎大小写当你访问
/user/eduardo
时,在User
的router-view
里面什么都不会呈现,因为没有匹配到嵌套路由。也许你确实想在那里渲染一些东西。在这种情况下,你可以提供一个空的嵌套路径}, ]
编程式导航
<router-link :to="...">
router.push(...)
比如我们可以使用下面的
router
path: 'emails'
具有上面三个router-view
,会映射到<router-view class="view main-content"></router-view>
path: 'profile'
具有上面三个router-view
,会分别映射到对应的name
重定向和别名
重定向
别名
类似重定向的一种模式 比如,将
/
别名为/home
,意味着当用户访问/home
时,URL 仍然是/home
,但会被匹配为用户正在访问/
别名可以使用数组表示多个
别名也可以使用动态映射
(重要)路由组件之间的传参
路由params自动转化为props
注意要
routes
声明props: true
才能自动转化遇到命名视图时,要分开声明
props: true
在
routes
直接传递props
当 props 是一个对象时,它将原样设置为组件 props。当 props 是静态的时候很有用
在
routes
将query
转化为静态的props
值URL
/search?q=vue
将传递{query: 'vue'}
作为props
传给SearchUser
组件。导航守卫
注意
next
的正确调用全局前置守卫
你可以使用 router.beforeEach 注册一个全局前置守卫:
全局解析守卫
全局后置钩子
这些钩子不会接受 next 函数也不会改变导航本身:
它们也反映了 navigation failures 作为第三个参数:
路由独享的守卫
组件beforeEnter
beforeEnter 守卫 只在进入路由时触发,不会在 params、query 或 hash 改变时触发。例如,从 /users/2 进入到 /users/3 或者从 /users/2#info 进入到 /users/2#projects。它们只有在 从一个不同的 路由导航时,才会被触发。
组件内守卫
beforeRouteLeave
: 通常用来预防用户在还未保存修改前突然离开。该导航可以通过返回 false 来取消组合式API写法
完整的导航解析流程
数据获取
导航完成之后获取
在导航完成前获取数据
如果复用一个路由,比如
/user/:id
会导致不同的path
会使用同一个路由,那么就不会调用beforeRouteEnter
,因此我们需要在beforeRouteUpdate
获取数据useRouter和useRoute
route 对象是一个响应式对象,所以它的任何属性都可以被监听,但你应该避免监听整个 route 对象。在大多数情况下,你应该直接监听你期望改变的参数。 router具有
push
、replace
等方法切换不同路由时的动画效果
也可以使用
afterEach
进行meta
字段的赋值滚动行为
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。
当创建一个 Router 实例,你可以提供一个 scrollBehavior 方法:
滚动到顶部
滚动到某一个元素的位置
滚动到锚点
不滚动/模仿原生滚动
如果返回
false
,或者是一个空对象,那么不会发生滚动返回 savedPosition,在按下 后退/前进 按钮时,就会像浏览器的原生表现那样:
延迟滚动
打包分chunk
webpack
webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中。
vite
在Vite中,你可以在rollupOptions下定义分块:
导航故障
触发router.push仍然留在原页面
使用promise检测是否导航跳转成功
使用API提示用户跳转失败
导航跳转失败类型
aborted
:在导航守卫中返回false
中断了本次导航。cancelled
: 在当前导航还没有完成之前又有了一个新的导航。比如,在等待导航守卫的过程中又调用了router.push
。duplicated
:导航被阻止,因为我们已经在目标位置了。