起步
项目中安装router
Vue3安装router4版本
Vue2安装router3版本
项目src目录下新建router文件夹,新建index.ts
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
|
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
const routes = <RouteRecordRaw[]> [ { path: "/", name: "Home", component: () => import("@/layout/login/index.vue") }, { path: '/register', name: 'Register', component: () => import('@/layout/register/index.vue') } ]
const router = createRouter( { history: createWebHistory(), routes } )
export default router
|
main.ts挂载路由index.ts
1
| import router from '@/router/index'
|
组件中通过router-link使用定义的路由,router-link使得 Vue Router 可以在不重新加载页面的情况下更改 URL,处理 URL 的生成以及编码。
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
| <!-- * @Description: * @Author: xiuji * @Date: 2022-07-12 16:30:05 * @LastEditTime: 2023-05-10 13:57:05 * @LastEditors: Do not edit --> <template> <!--使用 router-link 组件进行导航 --> <!--通过传递 `to` 来指定链接 --> <!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签--> <router-link to="/">login</router-link> <router-link to="/register">register</router-link> <hr> <!-- 路由出口 --> <!-- 路由匹配到的组件将渲染在这里 --> <router-view></router-view> </template>
<script setup lang="ts"> // vue版本^3.2.25,使用router-link,router-view需要使用import导入 import { RouterView, RouterLink } from "vue-router"; </script>
<style lang="scss"> html, body, #app { height: 100%; overflow: hidden; } </style>
|
命名路由-编程式导航
命名路由
除了 path
之外,你还可以为任何路由提供 name
。这有以下优点:
- 没有硬编码的 URL
params
的自动编码/解码。
- 防止你在 url 中出现打字错误。
- 绕过路径排序(如显示一个)
router-link使用name属性进行跳转需要改变,变为对象并且有对应的name
1
| <router-link :to="{ name: 'Register' }">register</router-link>
|
编程式导航
除了使用 <router-link>
创建 a 标签来定义导航链接,还可以借助 router 的实例方法,通过编写代码来实现。
字符串模式
1 2 3 4 5 6
| import { useRouter } from 'vue-router' const router = useRouter()
const toPage = () => { router.push('/') }
|
对象模式
1 2 3 4 5 6 7 8
| import { useRouter } from 'vue-router' const router = useRouter() const toPage = () => { router.push({ path: '/' }) }
|
命名式路由模式
1 2 3 4 5 6 7 8
| import { useRouter } from 'vue-router' const router = useRouter() const toPage = () => { router.push({ name: 'Home' }) }
|
a标签跳转
直接通过a href也可以跳转但是会刷新页面
历史记录
replace替换当前位置
采用replace进行页面的跳转会同样也会创建渲染新的Vue组件,但是在history中其不会重复保存记录,而是替换原有的vue组件。它的作用类似于 router.push
,唯一不同的是,它在导航时不会向 history 添加新记录,正如它的名字所暗示的那样——它取代了当前的条目。
声明式:
1
| <router-link :to="..." replace>
|
编程式:
也可以直接在传递给 router.push
的 routeLocation
中增加一个属性 replace: true
:
1 2 3 4 5 6
| import { useRouter } from 'vue-router' const router = useRouter()
router.push({ path: '/home', replace: true })
router.replace({ path: '/home' })
|
横跨历史
该方法采用一个整数作为参数,表示在历史堆栈中前进或后退多少步,类似于 window.history.go(n)
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { useRouter } from 'vue-router' const router = useRouter()
router.go(1)
router.go(-1)
router.go(3)
router.go(-100) router.go(100)
|
路由传参
query路由传参
query传参必须配合path使用,编程式导航,使用router push或者repalce的时候,改为对象形式新增query必须传入一个对象。
1 2 3 4 5 6 7 8 9
| import { useRouter } from 'vue-router' const router = useRouter() router.push({ path:'/register', query:{ id:1, type:'change' } })
|
接收路由参数
1 2 3
| import { useRoute } from 'vue-router' const route = useRoute() console.log(route.query)
|
params路由传参
params传参必须配合name使用,parmas传递参数的时候,不会在地址栏展示。
1 2 3 4 5 6 7
| import { useRouter } from 'vue-router' const router = useRouter()
router.push({ name:'你路由中的name', params:'是一个对象' })
|
接收路由参数
1 2 3 4
| import { useRoute } from 'vue-router' const route = useRoute()
console.log(route.params)
|
params传参是通过内存来传递参数的,所以刷新页面参数肯定会丢失。可以通过动态路由传递参数来解决
动态路由传参
很多时候,需要将给定匹配模式的路由映射到同一个组件。例如,可能有一个 User
组件,它应该对所有用户进行渲染,但用户 ID 不同。在 Vue Router 中,可以在路径中使用一个动态字段来实现,称之为 路径参数 :
1 2 3 4
| const routes = [ { path: '/users/:id', component: User }, ]
|
现在像 /users/123
和 /users/321
这样的 URL 都会映射到同一个路由。
路径参数 用冒号 :
表示。当一个路由被匹配时,它的 params 的值将在每个组件中以 $route.params
的形式暴露出来。因此,可以通过更新 User
的模板来呈现当前的用户 ID:
1 2 3
| const User = { template: '<div>User {{ $route.params.id }}</div>', }
|
你可以在同一个路由中设置有多个 路径参数,它们会映射到 $route.params
上的相应字段。例如:
匹配模式 |
匹配路径 |
$route.params |
/users/:username |
/users/eduardo |
{ username: 'eduardo' } |
/users/:username/posts/:postId |
/users/eduardo/posts/123 |
{ username: 'eduardo', postId: '123' } |
Path与Params区别
- query 传参配置的是 path,而 params 传参配置的是name,在 params中配置 path 无效
- query 在路由配置不需要设置参数,而 params 必须设置
- query 传递的参数会显示在地址栏中
- params传参刷新会无效,但是 query 会保存传递过来的值,刷新不变
嵌套路由
一些应用程序的 UI 由多层嵌套的组件组成。在这种情况下,URL 的片段通常对应于特定的嵌套组件结构,例如:
1 2 3 4 5 6 7 8
| /user/johnny/profile /user/johnny/posts +------------------+ +-----------------+ | User | | User | | +--------------+ | | +-------------+ | | | Profile | | +------------> | | Posts | | | | | | | | | | | +--------------+ | | +-------------+ | +------------------+ +-----------------+
|
通过 Vue Router,可以使用嵌套路由配置来表达这种关系。
1 2 3
| <div id="app"> <router-view></router-view> </div>
|
1 2 3 4 5 6
| const User = { template: '<div>User {{ $route.params.id }}</div>', }
const routes = [{ path: '/user/:id', component: User }]
|
这里的 <router-view>
是一个顶层的 router-view
。它渲染顶层路由匹配的组件。同样地,一个被渲染的组件也可以包含自己嵌套的 <router-view>
。例如,可以在 User
组件的模板内添加一个 <router-view>
:
1 2 3 4 5 6 7 8
| const User = { template: ` <div class="user"> <h2>User {{ $route.params.id }}</h2> <router-view></router-view> </div> `, }
|
要将组件渲染到这个嵌套的 router-view
中,需要在路由中配置 children
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const routes = [ { path: '/user/:id', component: User, children: [ { path: 'profile', component: UserProfile, }, { path: 'posts', component: UserPosts, }, ], }, ]
|
注意,以 /
开头的嵌套路径将被视为根路径。这允许你利用组件嵌套,而不必使用嵌套的 URL。
如你所见,children
配置只是另一个路由数组,就像 routes
本身一样。因此,你可以根据自己的需要,不断地嵌套视图。
嵌套的命名路由
1 2 3 4 5 6 7 8 9
| const routes = [ { path: '/user/:id', component: User, children: [{ path: '', name: 'user', component: UserHome }], }, ]
|
这将确保导航到 /user/:id
时始终显示嵌套路由。
在一些场景中,你可能希望导航到命名路由而不导航到嵌套路由。例如,你想导航 /user/:id
而不显示嵌套路由。那样的话,你还可以命名父路由,但请注意重新加载页面将始终显示嵌套的子路由,因为它被视为指向路径/users/:id
的导航,而不是命名路由:
1 2 3 4 5 6 7 8
| const routes = [ { path: '/user/:id', name: 'user-parent', component: User, children: [{ path: '', name: 'user', component: UserHome }], }, ]
|
命名视图
想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar
(侧导航) 和 main
(主内容) 两个视图,这个时候命名视图就派上用场了。可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view
没有设置名字,那么默认为 default
。
1 2 3
| <router-view class="view left-sidebar" name="AAA"></router-view> <router-view class="view main-content"></router-view> <router-view class="view right-sidebar" name="BBB"></router-view>
|
一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components
配置 (带上 s):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import AAA from"@/layout/AAA.vue" import BBB from"@/layout/BBB.vue" const router = createRouter({ history: createWebHashHistory(), routes: [ { path: '/', components: { default: Home, AAA, BBB, }, }, ], })
|
重定向和别名
重定向通过 routes
配置来完成,下面例子是从 /home
重定向到 /
:
1
| const routes = [{ path: '/home', redirect: '/' }]
|
重定向的目标也可以是一个命名的路由:
1
| const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
|
甚至是一个方法,动态返回重定向目标:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const routes = [ { path: '/search/:searchText', redirect: to => { return { path: '/search', query: { q: to.params.searchText } } }, }, { path: '/search', }, ]
|
请注意,**导航守卫并没有应用在跳转路由上,而仅仅应用在其目标上**。在上面的例子中,在 /home
路由中添加 beforeEnter
守卫不会有任何效果。
在写 redirect
的时候,可以省略 component
配置,因为它从来没有被直接访问过,所以没有组件要渲染。唯一的例外是嵌套路由:如果一个路由记录有 children
和 redirect
属性,它也应该有 component
属性。
相对重定向
可以重定向到相对位置:
1 2 3 4 5 6 7 8 9 10 11 12
| const routes = [ { path: '/users/:id/posts', redirect: to => { return 'profile' }, }, ]
|
别名alias
将 /
别名为 /home
,意味着当用户访问 /home
时,URL 仍然是 /home
,但会被匹配为用户正在访问 /
。
1
| const routes = [{ path: '/', component: Homepage, alias: '/home' }]
|
通过别名,你可以自由地将 UI 结构映射到一个任意的 URL,而不受配置的嵌套结构的限制。使别名以 /
开头,以使嵌套路径中的路径成为绝对路径。你甚至可以将两者结合起来,用一个数组提供多个别名:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const routes = [ { path: '/users', component: UsersLayout, children: [ { path: '', component: UserList, alias: ['/people', 'list'] }, ], }, ]
|
如果路由有参数,请确保在任何绝对别名中包含它们:
1 2 3 4 5 6 7 8 9 10 11 12 13
| const routes = [ { path: '/users/:id', component: UsersByIdLayout, children: [ { path: 'profile', component: UserDetails, alias: ['/:id', ''] }, ], }, ]
|
导航守卫
vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。这里有很多方式植入路由导航中:全局的,单个路由独享的,或者组件级的。
全局前置守卫
使用 router.beforeEach
注册一个全局前置守卫:
1 2 3 4 5 6 7
| const router = createRouter({ ... })
router.beforeEach((to, from) => { return false })
|
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中。
每个守卫方法接收三个参数:
1 2 3 4 5
| to: Route, 即将要进入的目标 路由对象; from: Route,当前导航正要离开的路由; next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。 next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。 next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。
|
案例:权限判断
1 2 3 4 5 6 7 8 9 10 11 12 13
| const whileList = ['/'] router.beforeEach((to, from, next) => { let token = localStorage.getItem('token') if (whileList.includes(to.path) || token) { next() } else { next({ path:'/' }) } })
|
如果遇到了意料之外的情况,可能会抛出一个 Error
。这会取消导航并且调用 router.onError()
注册过的回调。
如果什么都没有,undefined
或返回 true
,则导航是有效的,并调用下一个导航守卫
以上所有都同 async
函数 和 Promise 工作方式一样:
1 2 3 4 5
| router.beforeEach(async (to, from) => { const canAccess = await canUserAccess(to) if (!canAccess) return '/login' })
|
全局路由后置钩子
全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身:
它们对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用。
使用场景一般可以用来做loadingBar
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| <!-- * @Description: * @Author: xiuji * @Date: 2023-06-21 09:36:39 * @LastEditTime: 2023-06-21 14:33:28 * @LastEditors: Do not edit --> <template> <div class="loading_bar"> <div class="bar" ref="bar"> </div> </div> </template>
<script setup lang="ts"> import { ref } from 'vue'; let process = ref<number>(1); let bar = ref<HTMLElement>(); let timer = ref<number>(0); const startLoading = () => { let dom = bar.value as HTMLElement; process.value = 1; timer.value = window.requestAnimationFrame(function go() { if (process.value < 90) { process.value += 1; dom.style.width = process.value + '%'; dom.style.transition = 'width 0.5s'; timer.value = window.requestAnimationFrame(go); // 递归调用go方法 } else { process.value = 1; window.cancelAnimationFrame(timer.value); } }); };
const endLoading = () => { let dom = bar.value as HTMLElement;
window.requestAnimationFrame(() => { process.value = 100; dom.style.width = process.value + '%'; dom.style.opacity = '0'; dom.style.transition = 'opacity 1.5s'; }); };
defineExpose({ startLoading, endLoading }); </script>
<style scoped lang="scss"> .loading_bar { position: fixed; top: 0; width: 100%; height: 2px;
.bar { height: inherit; // 继承父元素的高度 width: 0; background-color: #409eff; } } </style>
|
在前置守卫中调用组件的startLoading方法,后置守卫中调用endLoading方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { createVNode, render } from 'vue' import router from '@/router/index' import ProcessBar from '@/components/loadingBar/index.vue'
const LoadingBar = createVNode(ProcessBar) render(LoadingBar, document.body)
router.beforeEach((to, from, next) => { if (whiteList.includes(to.path)) { LoadingBar.component?.exposed?.startLoading() next() } })
router.afterEach((to, from) => { LoadingBar.component?.exposed?.endLoading() })
|
路由元信息
通过接收属性对象的meta
属性来实现过渡名称、谁可以访问路由等功能,并且它可以在路由地址和导航守卫上都被访问到。
使用路由元信息可以在路由中附加自定义的数据,例如:
- 权限校验标识。
- 路由组件的过渡名称。
- 路由组件持久化缓存 (keep-alive) 的相关配置。
- 标题名称
可以在导航守卫或者是路由对象中访问路由的元信息数据。
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
| import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
declare module 'vue-router' { interface RouteMeta { title: string } }
const routes = <RouteRecordRaw[]> [ { path: "/", name: "Home", component: () => import("@/layout/login/index.vue"), meta: { title: '登录' } }, { path: '/register', name: 'Register', component: () => import('@/layout/register/index.vue'), meta: { title: '注册' } } ]
const router = createRouter( { history: createWebHistory(), routes } )
export default router
|
路由前置守卫中使用路由元信息修改页面标题
1 2 3 4 5 6 7 8 9
| const whiteList = ['/', '/register']
router.beforeEach((to, from, next) => { LoadingBar.component?.exposed?.startLoading() document.title = to.meta.title if (whiteList.includes(to.path)) { next() } })
|
路由过渡动效
想要在你的路径组件上使用转场,并对导航进行动画处理,你需要使用 v-slot API:
1 2 3 4 5
| <router-view #default="{ route, Component }"> <transition :enter-active-class="`animate__animated ${route.meta.transitionName}`"> <component :is="Component"></component> </transition> </router-view>
|
单个路由的过渡
上面的用法会对所有的路由使用相同的过渡。如果你想让每个路由的组件有不同的过渡,你可以将元信息和动态的 name
结合在一起,放在<transitionName>
上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const routes = <RouteRecordRaw[]> [ { path: "/", name: "Home", component: () => import("@/layout/login/index.vue"), meta: { title: '登录', transitionName: 'animate__wobble' } }, { path: '/register', name: 'Register', component: () => import('@/layout/register/index.vue'), meta: { title: '注册', transitionName: 'animate__bounce' } } ]
|
基于路由的动态过渡
也可以根据目标路由和当前路由之间的关系,动态地确定使用的过渡。使用和刚才非常相似的片段:
1 2 3 4 5 6
| <router-view v-slot="{ Component, route }"> <transition :enter-active-class="route.meta.transitionName"> <component :is="Component" /> </transition> </router-view>
|
可以添加一个 after navigation hook,根据路径的深度动态添加信息到 meta
字段。
1 2 3 4 5
| router.afterEach((to, from) => { const toDepth = to.path.split('/').length const fromDepth = from.path.split('/').length to.meta.transitionName = toDepth < fromDepth ? 'animate__wobble' : 'animate__bounce' })
|
强制在复用的视图之间进行过渡
Vue 可能会自动复用看起来相似的组件,从而忽略了任何过渡。幸运的是,可以添加一个 key
属性来强制过渡。这也允许你在相同路由上使用不同的参数触发过渡:
1 2 3 4 5
| <router-view v-slot="{ Component, route }"> <transition :enter-active-class="route.meta.transitionName"> <component :is="Component" :key="route.path"/> </transition> </router-view>
|
滚动行为
使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。
注意: 这个功能只在支持 history.pushState 的浏览器中可用。
当创建一个 Router 实例,你可以提供一个 scrollBehavior
方法:
1 2 3 4 5 6 7
| const router = createRouter({ history: createWebHashHistory(), routes: [...], scrollBehavior (to, from, savedPosition) { } })
|
scrollBehavior
函数接收 to
和 from
路由对象。第三个参数 savedPosition
,只有当这是一个 popstate
导航时才可用(由浏览器的后退/前进按钮触发)。
该函数可以返回一个 ScrollToOptions
位置对象:
1 2 3 4 5 6
| const router = createRouter({ scrollBehavior(to, from, savedPosition) { return { top: 0 } }, })
|
你也可以通过 el
传递一个 CSS 选择器或一个 DOM 元素。在这种情况下,top
和 left
将被视为该元素的相对偏移量。
1 2 3 4 5 6 7 8 9 10 11
| const router = createRouter({ scrollBehavior(to, from, savedPosition) { return { el: '#main', top: -10, } }, })
|
如果返回一个 falsy 的值,或者是一个空对象,那么不会发生滚动。
返回 savedPosition
,在按下 后退/前进 按钮时,就会像浏览器的原生表现那样:
1 2 3 4 5 6 7 8 9
| const router = createRouter({ scrollBehavior(to, from, savedPosition) { if (savedPosition) { return savedPosition } else { return { top: 0 } } }, })
|
如果你要模拟 “滚动到锚点” 的行为:
1 2 3 4 5 6 7 8 9
| const router = createRouter({ scrollBehavior(to, from, savedPosition) { if (to.hash) { return { el: to.hash, } } }, })
|
如果你的浏览器支持滚动行为,你可以让它变得更流畅:
1 2 3 4 5 6 7 8 9 10
| const router = createRouter({ scrollBehavior(to, from, savedPosition) { if (to.hash) { return { el: to.hash, behavior: 'smooth', } } } })
|
延迟滚动
有时候,需要在页面中滚动之前稍作等待。例如,当处理过渡时,希望等待过渡结束后再滚动。要做到这一点,你可以返回一个 Promise,它可以返回所需的位置描述符。下面是一个例子,在滚动前等待 500ms:
1 2 3 4 5 6 7 8 9
| const router = createRouter({ scrollBehavior(to, from, savedPosition) { return new Promise((resolve, reject) => { setTimeout(() => { resolve({ left: 0, top: 0 }) }, 500) }) }, })
|