路由标签
- router-link:to属性指定组件的链接
- router-view:路由出口,to指定的路由组件渲染在该标签内
路由模式
- 由mode改为history,并且属性值也调整
- history模式:history: createWebHistory()
- hash模式:history: createWebHashHistory()
- abstract模式:history: createMemoryHistory()
创建一个路由文件
1 | 1.引入路由api |
- 在main.js文件创建并挂载根实例
1
2
3const app = Vue.createApp({})
app.use(router)
app.mount('#app') - 在组件页面中使用:在任意组件中以 this.$router 的形式访问全局的router,以this.$route 的形式访问当前组件的路由,在setup() 函数中访问路由,可以调用useRouter 或 useRoute 函数
响应路由参数的变化
- 带参的路由导航到另一个带参路由,相同的组件实例将被重复使用,也意味着组件的生命周期钩子不会被调用
1
2
3
4
5
6
7
8
9
10const User = {
template: '<template>{{ $route.params.id }}<template>'
created(){
this.$watch(
()=> this.$route.params,
(toParams, previousParams) => {
// 对路由变化做出响应
}
)
} - beforeRouteUpdate(to,from){}:取消导航(搭配async/await)
路由的匹配语法
匹配所有内容放在$route.params.xxx下
- 示例:
{ path: '/:xxx(.*)*', name: 'NotFound', component: NotFound }
- 示例:
匹配以/xxx开头的所有内容,并将其放在$route.params.afterUser下
- 示例:
{ path: '/xxx:afterUser(.*)',component: UserGeneric }
- 示例:
在参数中自定义正则 — 适用范围区分同一路径不同参数的路由
1 | const routes = [ |
可重复的参数
- 在参数后面添加
*
表示匹配(0~n)个参数:{ path: ‘/:chapters*’ } - 在参数后面添加+表示匹配(1~n)个参数:{ path: ‘/:chapters+’ }
- 使用命名路由时该参数
需要传递一个数组
- 使用命名路由时该参数
- 在参数后面添加
Sensitive 与 strict 路由配置
- 当 strict: true 时,只能匹配不带有尾部斜线的路由(/xx/)
- 当 sensitive: true 时,区分大小写
- 当前路由上的 strict 或 sensitive 可以覆盖全局
1
2
3
4
5
6
7const router = createRouter({
routes: [
{ path: '/users/:id?', sensitive: true },
// ?将参数(0个或一个)标记为可选,且参数不能重复
]
strict: true, // 应用于全部路由
})
编程式导航(可参考vue2)
字符串路径
- router.push(‘/users/eduardo’)
带有路径的对象
- router.push({ path: ‘/users/eduardo’ })
命名的路由,并加上参数
- 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’ })
router.push — 向history栈添加一个新记录
router.replace — 类似router.push,但不会向history添加新记录
router.go(整数作参) — 表示在历史堆前进或退后几步
- router.go(1) // 前进一条记录
- router.go(-1) // 返回一条记录
命名视图
- 同时展示多个视图,而不是嵌套展示。用于同一路由多个视图
- 在路由里面的components配置好多个组件,
- 然后页面的使用多个router-view标签,带有name属性的会跟默认视图同一级渲染,不带的则是嵌套渲染在同一个router-view标签,注意name的值要和组件的名字一样
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13<router-view class="view main-content"></router-view>
<router-view class="view right-sidebar" name="RightSidebar"></router-view>
const router = createRouter({
history: createWebHashHistory(),
routes: [{
path: '/',
components: {
default: Home,
LeftSidebar,
RightSidebar,// 它们与 `<router-view>` 上的 `name` 属性匹配
},
},],
})
重定向和别名
- 配置在路由路由表上的路由
redirect: 重定向
- 直接写路径:redirect: ‘/‘
- 重定向到命名路由:redirect: { name: ‘homepage’ }
- 重定向一个方法,动态:
1
2
3
4
5
6const routes = [{
path: '/search/:searchText',
redirect: to => { // 可以省略component
return { path: 'search', query: { q: to.params.searchText } }
}
}]
alias:给路径起别名
相对重定向
1 | const routes = [ |
将 props 传递给路由组件
- 在路由表上的路由配置,路由参数和prop的参数名要相同
- 以数组形式传递:props: [‘id’]
- 以布尔值传递:把路由收到的所有params参数通过props传给该路由组件
- 示例:path:’/user/:params’,props:true,
- 为每个有命名视图的路由定义props
- props: { default: true, 命名视图路由名: false }
- 函数模式:传递query参数通过props传递给组件
- 示例:props: route => ({ query: route.query.q })
导航守卫
- 全局前置守卫 – router.beforeEach
- 全局解析守卫 – router.beforeResolve
- 全局后置钩子 – router.afterEach
- 路由独享的守卫 – routes 里面 beforeEnter 属性
- 组件内的守卫 – 在组件页面中使用的钩子:{beforeRouteEnter(){},beforeRouteUpdate(){},beforeRouteLeave(){}}
钩子的参数解析
- to: 即将要进入的目标
- from: 当前导航正要离开的路由
- next:多调用一次
- isAuthenticated:用户是否登录过
- to.name !== ‘路由名’ : 避免无限重定向
- return false: 取消导航
全局前置守卫
- 当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve完之前一直处于等待中
1
2
3
4router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else next()
})
全局解析守卫
- 每次导航时都会触发,但是确保在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用。是获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15router.beforeResolve(async to => {
if (to.meta.requiresCamera) {
try {
await askForCameraPermission()
} catch (error) {
if (error instanceof NotAllowedError) {
// ... 处理错误,然后取消导航
return false
} else {
// 意料之外的错误,取消导航并把错误传给全局处理器
throw error
}
}
}
})
全局后置钩子
- 不接受next 函数也不改变导航本身,对于分析、更改页面标题、声明页面等辅助功能有用
1
2
3router.afterEach((to, from, failure) => {
if (!failure) sendToAnalytics(to.fullPath)
})
路由独享的守卫
- 只在进入路由时触发,不会在params、query或hash改变时触发,只有在从一个不同的路由导航时,才会被触发。
1
2
3
4
5
6
7
8
9
10const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from) => {
// reject the navigation
return false
},
},
]
组件内的守卫
- beforeRouteEnter(to, from, next) {}
- 在渲染该组件的对应路由被验证前调用
- 不能获取组件实例this
- 因为当守卫执行时,组件实例还没被创建!
1
2
3
4
5beforeRouteEnter(to, from, next) {
next(vm => {
/* 通过vm访问组件实例*/
})
}
- beforeRouteUpdate(to, from) {}
- 在当前路由改变,但是该组件被复用时调用
- 举例: 对于一个带有动态参数的路径
/users/:id
,在/users/1
和/users/2
之间跳转的时候, - 由于会渲染同样的
UserDetails
组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 - 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例
this
- beforeRouteLeave(to, from) {}
- 在导航离开渲染该组件的对应路由时调用
- 与 beforeRouteUpdate 一样,它可以访问组件实例 this
- 通常用来预防用户在还未保存修改前突然离开
1
2
3
4beforeRouteLeave(to, from) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (!answer) return false
}
导航解析流程
- 导航被触发
- 在失活的组件里调用 beforeRouteLeave 守卫
- 调用全局的 beforeEach 守卫
- 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)
- 在路由配置里调用 beforeEnter
- 解析异步路由组件
- 在被激活的组件里调用 beforeRouteEnter
- 调用全局的 beforeResolve 守卫(2.5+)
- 导航被确认
- 调用全局的 afterEach 钩子
- 触发 DOM 更新
- 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入
路由元信息 — meta 属性添加附加信息,调用$route.meta
数据获取
- 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示。
- 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。
- 导航完成后获取数据 — 会马上导航和渲染组件,然后在组件的 created 钩子中获取数据。
- 在created()中watch路由参数,调用methods中的状态,展示一些状态,当然这些状态要在data中初始化
- 在导航完成前获取数据 — 导航转入新的路由前获取数据
- 在 beforeRouteEnter 守卫中获取数据,当数据获取成功后只调用 next 方法。建议在数据获取期间,显示一些进度条或者别的指示
Vue Router 和 组合式API(在setup中使用)
- 访问路由和当前路由:使用useRouter和useRoute替代this.$router和this.$route
组件内的API导航守卫
- 组合式API守卫可以用在任何
<router-view>
渲染的组件,不必像组件内守卫那样直接用在路由组件上 - 引入:
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
- 除了不能访问this其它一样
useLink / 扩展RouterLink
- 页面存在router-link标签:
<router-link v-solt="{props1, props2, props3}">
- 编码时引入:
import { RouterLink, useLink } from 'vue-router'
- 在props配置项中使用:
...RouterLink.props
传入router-link标签上的props - 在setup中传入props:
setup(props){}
- 拿到props: 在setup中使用
useLink(props)
并通过解构的方式拿到
过渡动效
在路径组件上使用转场,并对导航进行动画处理
- 示例:
1
2
3
4
5<router-view v-slot="{ Component }">
<transition name="fade">
<component :is="Component" />
</transition>
</router-view>
- 示例:
单个路由的过渡 — 元信息meta和
上的动态的name - 示例:
1
2
3
4
5
6
7
8
9
10const routes = [{
path: '/custom-transition',
component: PanelLeft,
meta: { transition: 'slide-left' },
},]
<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition || 'fade' ">
<component :is="Component" />
</transition>
</router-view>
- 示例:
基于路由的动态过渡 — 根据路径的深度动态添加信息到 meta 字段
- 示例:
1
2
3
4
5
6
7
8
9
10<router-view v-slot="{ Component, route }">
<transition :name="route.meta.transition">
<component :is="Component" />
</transition>
</router-view>
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'
})
- 示例:
强制在复用的视图之间进行过渡
- 在component标签上再添加一个key属性来强制过渡
滚动行为
- 只在支持 history.pushState 的浏览器中可用
创建router时提供scrollBehavior(to, from, savedPosition){}方法
- savedPosition由浏览器的后退/前进按钮触发
- return返回期望滚到那个位置
- 始终滚动到顶部:return { top: 0 }
- 不滚动:return false | return null
- 按下后退/前进按钮时就会像浏览器原生表现: return savedPosition
可以返回一个ScrollToOptions位置对象
通过el传递一个CSS选择器或者一个DOM元素,此时top和left将被视为该元素的相对偏移量
- 示例:
1
2
3
4
5
6
7
8const router = createRouter({
scrollBehavior(to, from, savedPosition) {
return {
el: '#main',
top: -10,
}
},
})
滚动到锚点行为
- return 时添加behavior: ‘smooth’这个配置
延迟滚动
- return new Promise((resolve, reject) => {延时器}),可以返回所需的位置描述符
路由懒加载
动态导入代替静态导入
- 不使用import…from…使用 const 组件名 = () => import(‘路径’)
- 路由配置的component接受一个返回Promise组件的函数,vue router只会在第一次进入页面时才会获取这个函数,然后使用缓存数据,不要在路由中使用异步组件
- 示例:
1
2
3const UserDetail = () => {
Promise.resolve({/*组件定义*/})
}
- 示例:
把组件按组分块
- 使用webpack — 把某个路由下的所有组件都打包在同个异步块chunk中
- webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中
- 即使用动态路径导入多个路径不同组件名相同的文件
- 使用Vite
- 在vite.config.js文件下的build –> rollupOptions –> output –> manualChunks中配置
- 以’组名’: [‘路径1’,’路径2’,’路径3’]的形式添加到manualChunks中
导航故障
等待导航结果
- 导航是异步的,返回Promise:await router.push(‘/my-profile’)
- 导航到新页面后隐藏菜单:this.isMenuOpen = false
检测导航故障 — 失败返回false
- 引入import { NavigationFailureType, isNavigationFailure } from ‘vue-router’
- 将异步导航赋值给一个变量
- 检测试图离开未保存的编辑文本界面并在其中对用户进行提示操作:
- if(isNavigationFailure(failure, NavigationFailureType.aborted)){}
鉴别导航故障
- aborted:在导航守卫中返回false中断了本次导航
- cancelled:在当前导航还没有完成之前又有了一个新的导航
- duplicated:导航被阻止,因为我们已经在目标位置了
导航故障属性
- 所有的导航失败都会暴露 to 和 from 属性
- failure.to.path:反映失败导航的当前位置
- failure.from.path:反映失败导航的目标位置
检测重定向 — 不会阻止导航,而是创建一个新导航
- redirectedFrom 是解析出的路由地址,就像导航守卫中的 to和 from
- 示例:if (router.currentRoute.value.redirectedFrom) {}
动态路由
查看现有路由
- 检查路由是否存在:router.hasRoute()
- 获取一个包含所有路由记录的数组:router.getRoutes()
添加嵌套路由
- 使用router.addRoute来添加路由
- 示例:
1
2router.addRoute({ name: 'admin', path: '/admin', component: Admin })
router.addRoute('admin', { path: 'settings', component: AdminSettings })
删除路由 — 所有别名和子路由也会被删除
- 方法一:使用router.addRoute添加一个name相同的路由
- 方法二:调用router.addRoute返回的回调,存在就删除
- 方法三:使用router.removeRoute(‘name’)
- 注:当路由被删除时,所有的别名和子路由也会被同时删除
在导航守卫中添加路由
- hasNecessaryRoute():在添加新的路由后返回 false,以避免无限重定向