VueRouter快速手册
🫡🙃
[ ] 先看官方文档,然后组织出来笔记和案例代码
安装
1 | npm install vue-router@4 |
基础
两个标签
router-link
跳转没用<a>
标签,用<router-link>
标签,在不重新加载页面的情况下,改变 URL
标签属性:
- to:指定跳转的路径,最后编译成
<a>
标签的href
属性 - tag:指定渲染成什么标签,默认是
<a>
- replace:默认是
false
,点击跳转后,会向 history 添加一个新的记录,设置为true
,则不会添加新记录,替换当前的 history 记录 - active-class:指定当前路由高亮的类名,默认是
router-link-active
除了使用标签来导航(声明式导航),还可以使用router.push
方法,编程式导航
router-view
- 路由匹配到的组件将渲染在这里
- 一个页面中可以有多个
<router-view>
标签,用于显示不同的内容
创建路由
官网推荐的是通过动态导入组件来实现创建路由(路由懒加载)
📄router/index.ts: 创建路由
1 | import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router"; |
📄main.ts: 将路由当成插件挂在到 Vue 实例中
1 | import { createApp } from "vue"; |
📄Home.vue:两个标签,实现路由跳转和渲染
1 | <template> |
📄About.vue:在setup中通过 hooks 实现跳转、获取路由参数
1 | <template> |
- 在模板中可以访问到 $router 和 $route 对象,但是在 setup 中不能访问,需要通过
useRouter
和useRoute
获取
router
和route
的区别:router 是路由实例,route 是当前路由对象
在
setup
中使用的时候,useRouter
和useRoute
必须是顶级调用,如果在方法里面调用,得到的返回值是undefined
router 的一些方法:
router.push
:跳转到指定路由router.replace
:跳转到指定路由,不会在 history 中添加新记录router.go
:前进或后退指定步数router.back
:后退router.forward
:前进router.addRoute
:动态添加路由router.removeRoute
:动态删除路由router.getRoutes
:获取所有路由,是一个数组router.hasRoute
:判断是否有指定路由
route 对象是响应式的,可以通过watch
监听变化
1 | import { useRoute } from "vue-router"; |
动态路由
router.addRoute
只注册一个新路由,如果新增的路由与当前位置匹配,就得手动配置router.push
或者router.replace
跳转
1 | router.addRoute({ path: "/about", component: About }); |
- 如果需要等待新的路由显示,可以使用
await router.replace()
在导航守卫中添加路由的时候,使用return
返回新的路由,而不是使用router.push
或者router.replace
1 | router.beforeEach((to) => { |
hasNecessaryRoute()
在添加新的路由后返回false
,以避免无限重定向。
添加嵌套路由
1 | router.addRoute({ name: "admin", path: "/admin", component: Admin }); |
🗑️ 删除路由有几个方式:
- 直接使用
router.addRoute
,如果有重名的,先删除再添加
1 | router.addRoute({ path: "/about", name: "about", component: About }); |
- 通过
router.addRoute()
返回的回调函数来删除
1 | const removeRoute = router.addRoute(routeRecord); |
- 通过
router.removeRoute
来按名称删除
1 | router.addRoute({ path: "/about", name: "about", component: About }); |
数据获取
进入路由之后需要从服务器获取数据,一般两种方式:
- 导航完成之后获取,先导航,然后在组件的声明周期函数中获取。这期间显示 正在加载 z 之类的动画,然后也可以添加个
watch
以便再次获取 - 导航完成之前获取,在导航守卫中进行
编程式导航
🌎 这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,会回到之前的 URL。
- 点击导航标签,内部会调用
router.push
方法
push
方法的参数可以是一个字符串,也可以是一个描述地址的对象
1 | // 字符串路径 |
动态路由匹配
- 目的是为了根据不同的参数,渲染不同的内容
动态路由匹配的参数,可以通过useRoute
获取
useRoute
返回的是响应式的对象,所以可以通过watch
监听变化- 也可以通过
$route.params
获取,$route
不是响应式的
除了params
,还有query
、hash
等,params
和query
都是对象,hash
是字符串
1 | const User = { |
可以传递多个参数,它们会映射到 $route.params
上的相应字段
匹配模式 | 匹配路径 | $route.params |
---|---|---|
/users/:username | /users/eduardo | { username: ‘eduardo’ } |
/users/:username/posts/:postId | /users/eduardo/posts/123 | { username: ‘eduardo’, postId: ‘123’ } |
路由参数变化,组件不会重新渲染,因为组件的复用,可以通过
watch
监听参数变化,重新获取数据从而更新组件。或者使用导航守卫监听参数变化
1 | const User = { |
使用正则匹配
常规匹配的时候,内部会使用([^/]+)
来匹配参数
- 至少有一个字符不是
/
如果想根据参数的格式来匹配,可以使用自定义正则表达式
1 | const routes = [ |
\d
需要使用\\d
来转义
可重复的参数
将参数标记为可重复:
- 通过
+
来表示参数可以重复,可以是一个或多个- 就是说至少有一个字符不是
/
- 就是说至少有一个字符不是
- 通过
*
来表示参数可以重复,可以是零个或多个- 就是说可以是空字符串,也就是可选的
- 通过
?
也可以表示可选的
1 | const routes = [ |
在传递的时候,对应的参数需要传递数组
1 | // 给定 { path: '/:chapters*', name: 'chapters' }, |
router.resolve
方法可以根据路由名称和参数,返回一个 URL
同时可以加上正则表达式
1 | const routes = [ |
Sensitive 和 strict 路由
例如,路由 /users
将匹配 /users
、/users/
、甚至 /Users/
可通过全局配置配置:
1 | const router = createRouter({ |
也可以在路由配置中配置:
1 | const router = createRouter({ |
嵌套路由
一个页面中有多个<router-view>
标签,用于显示不同的内容
- 最外层的
<router-view>
标签,用于显示最外层的内容,也就是父路由的内容 - 一个被渲染的组件里面也可以有自己的
<router-view>
标签,用于显示内层的内容,也就是子路由的内容
1 | const routes = [ |
- 以
/
开头的嵌套路径将被视为根路径
如果访问了不存在的路由,<router-view>
里面什么都没有,可以通过<router-view v-slot="{ Component }">
来处理
1 | <router-view v-slot="{ Component }"> |
也可以提供一个空的嵌套路由作为默认子路由(path 传递空字符串)
1 | const routes = [ |
命名路由和命名视图
命名路由
给路由配置一个name
属性,可以通过name
来跳转
- 没有硬编码的 URL,没有硬编码的 URL,所以如果想改变 /user/:id,可以随时修改路由配置,而不用在代码中搜索所有用到该路径的地方
- params 的自动编码/解码。
- 防止你在 url 中出现打字错误。
- 绕过路径排序(如显示一个)/user/:id 和 /user/:username,因为后者是在前者之后定义的。
params 的自动编码/解码,更方便使用参数
1 | const router = createRouter({ |
或者在标签中使用
1 | <router-link :to="{ name: 'user', params: { username: 'erina' }}"> |
命名视图
给<router-view>
标签添加name
属性,可以在路由配置中指定渲染的组件。例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图
1 | <router-view class="view left-sidebar" name="LeftSidebar"></router-view> |
这个路由中有多个视图,所以得使用components
配置
1 | const router = createRouter({ |
重定向和别名
重定向
通过 routes 配置来完成
1 | const routes = [{ path: "/home", redirect: "/" }]; |
也可以传递一个命名的路由
1 | const routes = [{ path: "/home", redirect: { name: "home" } }]; |
甚至可以传递一个方法,动态返回重定向的目标
1 | const routes = [ |
重定向时可以不指定
component
,因为重定向的时候,没有组件需要渲染
相对重定向
- 重定向的目标也可以是相对路径,这样就不用写完整的路径了
1 | const routes = [ |
别名
- 重定向的时候
/home
会替换掉/
,别名不会改变 URL,但是会在路由匹配时添加到 history 记录中
所以应该这样配置
1 | const routes = [{ path: "/", component: Homepage, alias: "/home" }]; |
通过别名可以将 UI 结构映射到一个任意的 URL,而不受配置的嵌套结构的限制。使别名以 / 开头,以使嵌套路径中的路径成为绝对路径。你甚至可以将两者结合起来,用一个数组提供多个别名:
1 | const routes = [ |
路由组件传参
通过使用 props 将路由参数传递给组件
在组件中获取路由传过来的 props 时,需要定义然后使用
布尔模式:将props
设置为true
,route.params
会被设置为组件的 props
1 | const User = { |
替换成
1 | const User = { |
命名视图:每个视图需要定义自己的 props
1 | const routes = [ |
对象模式:如果 props 被设置为一个对象,它会被按原样设置为组件的 props
1 | const routes = [ |
函数模式:可以创建一个函数返回 props,将参数转换为其他类型,将静态值与基于路由的值相结合等等。
1 | const routes = [ |
路由元信息
配置meta
1 | const routes = [ |
- 数组中每个对象叫做路由记录,在嵌套路由中就分为父路由记录和子路由记录
- 匹配到的路由暴露为
$route
对象的matched
属性,是一个数组,包含了所有匹配到的路由记录
$route.meta
方法可以获取到路由元信息,直接用.
访问
1 | router.beforeEach((to, from) => { |
TypeScript
中,可以通过RouteMeta
来定义路由元信息的类型
1 | // typings.d.ts or router.ts |
props
会提供这个吗以后 ??🫡
不同的历史模式
在以往vue2使用的时候,使用mode
来配置,vue3现在使用history
来配置
它们之间的对应关系:
- mode: history | hash | abstract
- history: createWebHistory | createWebHashHistory | createMemoryHistory
👉 hash 模式
在内部传递实际 URL 之前使用了一个哈希字符(#
)
URL 看起来就像这样:http://oursite.com/#/user/id
它的好处是不需要后端配置,缺点是在 SEO 方面不太友好
👉 HTML5 模式
✅ 推荐使用
URL 开起来就像这样:http://oursite.com/user/id
它的好处是 URL 看起来更加友好,但是需要后端配置
进阶
导航守卫
导航守卫是一个函数,它会在路由发生变化时被调用
出现的地方有:全局的、单个路由独享的、组件内的
全局前置守卫
router.beforeEach
方法注册一个全局前置守卫,守卫是异步的,所以跳转需要在守卫resolve
之后才会继续
1 | const router = createRouter({ ... }) |
回调参数:
to
:即将要进入的目标路由对象from
:当前导航正要离开的路由next
:调用该方法后,能进入下一个钩子,不过现在可以用return
来代替next
next(false)
:取消当前导航next('/')
:跳转next({ path: '/' })
:跳转
现在 Vue Router 4 中可以使用return
,不用next()
,返回值可以有:
false
:取消当前导航- 一个路由地址:跳转到指定路由,类似于
router.push()
,也可以设置replace: true
、name: 'home'
等 - 如果什么都没有,
undefined
或返回true
,则导航是有效的,并调用下一个导航守卫
return 会比 next 好点吧应该,因为 next 必须得调用,而 return 可以不用写那么多 if else
如果遇到了意料之外的情况,可能会抛出一个 Error。这会取消导航并且调用 router.onError()
注册过的回调。
1 | router.beforeEach(async (to, from) => { |
全局解析守卫
router.beforeResolve
方法注册一个全局解析守卫,和前置导航守卫类似,但是得在 所有组件内守卫和 异步路由组件 被解析之后,解析守卫就被调用
示例:确保用户可访问到自定义meta
1 | router.beforeResolve(async (to) => { |
router.beforeResolve 是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置。
全局后置钩子
- 和守卫不同的是,后置钩子没有
next
函数,不能改变导航本身;第三个参数可以是failure
- 对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用。
1 | router.afterEach((to, from, failure) => { |
也就是导航故障
导航故障
异步等待导航结果
导航故障是导航期间出现的任何错误或被取消的导航。当使用 router-link
组件时,Vue Router 会自动调用 router.push
来触发一次导航。
一般来说导航都会到一个新的页面,想要在导航完成之后做一些事情,可以使用router.afterEach
,但是如果导航被取消了(或者错误之类的),就不会触发router.afterEach
解决的办法就是使用导航的异步特性进行等待
比如等导航结束之后再关闭菜单
1 | await router.push("/my-profile"); |
或者是监听导航故障,然后做点什么
1 | router.onError((error) => { |
故障
导航成功的话,await
等到的是undefined
,如果导航失败了,await
等到的是一个NavigationFailure
对象
1 | const navigationResult = await router.push("/my-profile"); |
或者是(真的吗)
1 | try { |
**isNavigationFailure()**
:来了解哪些导航被阻止了和为什么被阻止
1 | import { NavigationFailureType, isNavigationFailure } from "vue-router"; |
👉 isNavigationFailure()
第二个参数是一个字符串,可以是
aborted
:导航被取消,return false
或next(false)
cancelled
:当前导航没完成之前,又触发了一个新的导航(在导航等待的时候,调用了个新的router.push()
)duplicated
:导航被阻止,因为已经在目标位置了
如果第二个参数不传的话,就只是判断是否是Navigation Failure 🙃
👉 isNavigationFailure()
第一个参数,记录着路由的to
, from
检测重定向
重定向不会阻止导航,而是创建一个新的导航,所以router.push()
返回的是undefined
1 | await router.push("/my-profile"); |
过渡特效和滚动行为
过度特效
1 | <router-view v-slot="{ Component }"> |
Component
是当前路由匹配到的组件v-solt
可以用简写#default="{ Component }"
除了使用v-slot
,还可以使用<router-view>
的transition
属性
1 | <router-view transition="fade" /> |
🙃 针对每个路由配置不同的过渡特效
- 可以在路由配置中加入
meta
- 或者在全局后置钩子中加入
meta
1 | const routes = [ |
1 | <router-view v-slot="{ Component, route }"> |
❔❔ Vue 可能会自动复用看起来相似的组件,从而忽略了任何过渡。可以给动态组件加上一个 key
属性,来提示 Vue 去强制重新渲染
1 | <router-view v-slot="{ Component, route }"> |
滚动行为
- 就是在前进后退的时候,页面滚动到哪里,或者是保持原来的滚动位置
注意: 这个功能只在支持 history.pushState 的浏览器中可用。
1 | const router = createRouter({ |
- 返回一个falsy值,或者是一个空对象,不会发生滚动
- 返回
savedPosition
,会滚动到之前保存的位置
位置使用的是top
和left
,可以是数字或者是字符串
1 | const router = createRouter({ |
如果你要模拟 “滚动到锚点” 的行为:
1 | const router = createRouter({ |
延迟滚动,等待页面动效完成后再滚动,可以返回个Promise
1 | const router = createRouter({ |
VueRouter快速手册