vue-router相关知识
动态路由匹配
一个参数:
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User }
]
})
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
多个参数:
| 模式 | $route.params |
|---|---|
| /user/:username | { username: 'evan' } |
| /user/:username/post/:post_id | { username: 'evan', post_id: '123' } |
路由对象属性
- $route.path(string) 等于当前路由路径(url)
- $route.params(object) 对象包含了动态参数的key/value
- $route.query(object)
对象表示url的查询参数,例如路径
/foo?user=1,则有router.query.user==1,如果没有查询参数,则是个空对象。 - $route.hash(string)
当前路由的hash值(带
#),如果没有hash值,则为空字符串 - $route.fullPath(string) 完成解析后的URL,包含查询参数和hash的完整路径
- $route.matched(string)
一个数组,包含当前路由的所有嵌套路径片段的路由记录 。路由记录就是
routes配置数组中的对象副本 (还有在children数组)。
const router = new VueRouter({
routes: [
// 下面的对象就是路由记录
{
path: '/foo',
component: Foo,
children: [
// 这也是个路由记录
{ path: 'bar', component: Bar }
]
}
]
})
- $route.name(string) 等于当前路由的名称
- $route.redirectedFrom 如果存在重定向,即为重定向来源的路由的名字
路由参数变化,组件被复用
当使用路由参数时,原来的组件实例会被复用,不会重新创建和销毁,这就意味着组件的生命周期钩子不会再被调用 复用组件时,想对路由参数的变化作出响应的话,可在watch里检测$route对象:
watch: {
$route(to, from) {
// 对路由变化作出响应...
}
或者组件内调用beforeRouteUpdate钩子
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteUpdate具体用法:
export default{
data(){
return {}
},
beforeRouteUpdate(to,from,next){
console.log(to,from,next)
if(to.fullPath!=from.fullPath){
next()
this.changeUser()
}
},
methods:{
changeUser(){
getUserBaseInfo({userId:this.$route.params.id}).then(res=>{
if(res.success){
this.stuInfo = res.data;
}else{
this.$message.error(res.message)
}
})
}
}
}
子路由的path值
const User = {
template: `
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view></router-view>
</div>
`
}
const router = new VueRouter({
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
}
]
}
]
})
若想要被复用的组件被重新创建时(重新触发创建销毁钩子函数),可以给router-view添加key
<router-view :key="$route.params.id"></router-view>
编程式导航router.push()
等同于:声明式导航:<router-link :to="...">
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
值得注意的是: 1、router.push里同时传path和params时,params会被忽略,而query不存在这种情况。
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user
2、router.push里同时传路由的name和params时生效,或者手写完整的带参数的path
const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
3、如果目的地和当前路由相同,只有参数发生了改变 (比如从一个用户资料到另一个 /users/1 -> /users/2),你需要使用 beforeRouteUpdate 来响应这个变化 (比如抓取用户信息)。
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteUpdate具体用法:
export default{
data(){
return {}
},
beforeRouteUpdate(to,from,next){
console.log(to,from,next)
if(to.fullPath!=from.fullPath){
next()
this.changeUser()
}
},
methods:{
changeUser(){
getUserBaseInfo({userId:this.$route.params.id}).then(res=>{
if(res.success){
this.stuInfo = res.data;
}else{
this.$message.error(res.message)
}
})
}
}
}
路由重定向(重定向目标为静态或动态)
1.重定向也是通过 routes 配置来完成,下面例子是从 /a 重定向到 /b:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
]
})
2.重定向的目标也可以是一个命名的路由:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
})
3.甚至是一个方法,动态返回重定向目标:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}}
]
})
使用 props 将组件和路由解耦:
当路由中的props属性值为true时,route.params 将会被设置为组件属性。
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true },
// 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
})
其中template中的{{id}}相当于:{{ $route.params.id}}
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
hash模式和history模式
-
hash模式的#url从未发送到服务器,因此服务器层面上不需要做任何配置;
-
history模式,当使用这种历史模式时,URL 会看起来很 "正常",例 如
https://example.com/user/id。漂亮!
不过,问题来了。由于我们的应用是一个单页的客户端应用,如果没有适当的服务器配置,用户在浏览器中直接访问 https://example.com/user/id,就会得到一个 404 错误。这就尴尬了。
不用担心:要解决这个问题,你需要做的就是在你的服务器上添加一个简单的回退路由。如果 URL 不匹配任何静态资源,它应提供与你的应用程序中的 index.html 相同的页面。漂亮依旧!
服务器该如何设置,具体见vue-router官方文档
导航守卫
路由参数或查询的改变不会触发进入/离开导航守卫
全局前置守卫
router.beforeEach 注册一个全局前置守卫:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
router.beforeEach的返回值有两种情况:
false:取消当前导航,将url地址重置到from路由对应的地址- 一个路由地址:内容等同于router.push()中的选项
router.beforeEach(async (to, from) => {
if (
// 检查用户是否已登录
!isAuthenticated &&
// ❗️ 避免无限重定向
to.name !== 'Login'
) {
// 将用户重定向到登录页面
return { name: 'Login' }
}
})
第三个参数next:Function
- next():进入管道中的下一个钩子
- next(false):中断当前的导航,url地址重置到from路由对应的地址
- next('/')或者next({path: '/'}):跳转到一个不同的地址。参数等同于router.push()里的任意选项
- next(error):如果传入
next的参数是一个Error实例,则导航会被终止且该错误会被传递给router.onError()注册过的回调。
router.onError(callback)
全局解析守卫
是在所有组件内守卫和异步路由组件被解析完后才被调用
全局后置钩子
路由独享守卫
即在路由配置上直接定义beforeEnter守卫:
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
组件内的守卫
beforeRouteEnterbeforeRouteUpdate(2.2 新增)beforeRouteLeave
const Foo = {
template: `...`,
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
beforeRouteUpdate (to, from, next) {
// just use `this`
this.name = to.params.name
next()
}
beforeRouteLeave (to, from, next) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}
这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消。
路由元信息(meta)
有时,你可能希望将任意信息附加到路由上,如过渡名称、谁可以访问路由等。这些事情可以通过接收属性对象的meta属性来实现,并且它可以在路由地址和导航守卫上都被访问到。定义路由的时候你可以这样配置 meta 字段:
const routes = [
{
path: '/posts',
component: PostsLayout,
children: [
{
path: 'new',
component: PostsNew,
// 只有经过身份验证的用户才能创建帖子
meta: { requiresAuth: true }
},
{
path: ':id',
component: PostsDetail
// 任何人都可以阅读文章
meta: { requiresAuth: false }
}
]
}
]
那么如何访问这个 meta 字段呢?
首先,我们称呼 routes 配置中的每个路由对象为 路由记录。路由记录可以是嵌套的,因此,当一个路由匹配成功后,它可能匹配多个路由记录。
例如,根据上面的路由配置,/posts/new 这个 URL 将会匹配父路由记录 (path: '/posts') 以及子路由记录 (path: 'new')。
一个路由匹配到的所有路由记录会暴露为 $route 对象(还有在导航守卫中的路由对象)的$route.matched 数组。我们需要遍历这个数组来检查路由记录中的 meta 字段,但是 Vue Router 还为你提供了一个 $route.meta 方法,它是一个非递归合并所有 meta 字段的(从父字段到子字段)的方法。这意味着你可以简单地写
router.beforeEach((to, from) => {
// 而不是去检查每条路由记录
// to.matched.some(record => record.meta.requiresAuth)
if (to.meta.requiresAuth && !auth.isLoggedIn()) {
// 此路由需要授权,请检查是否已登录
// 如果没有,则重定向到登录页面
return {
path: '/login',
// 保存我们所在的位置,以便以后再来
query: { redirect: to.fullPath },
}
}
})
过渡动效
<router-view v-slot="{ Component }">
<transition name="fade">
<component :is="Component" />
</transition>
</router-view>
单个路由的过渡
如果你想让每个路由的组件有不同的过渡,你可以将元信息和动态的 name 结合在一起,放在<transition> 上:
const routes = [
{
path: '/custom-transition',
component: PanelLeft,
meta: { transition: 'slide-left' },
},
{
path: '/other-transition',
component: PanelRight,
meta: { transition: 'slide-right' },
},
]
Vue 可能会自动复用看起来相似的组件,从而忽略了任何过渡。幸运的是,可以添加一个 key 属性来强制过渡。这也允许你在相同路由上使用不同的参数触发过渡:
<router-view v-slot="{ Component, route }">
<transition name="fade">
<component :is="Component" :key="route.path" />
</transition>
</router-view>
滚动行为
路由懒加载
// 将
// import UserDetails from './views/UserDetails.vue'
// 替换成
const UserDetails = () => import('./views/UserDetails.vue')
const router = createRouter({
// ...
routes: [{ path: '/users/:id', component: UserDetails }],
})
动态路由
对路由的添加通常是通过 routes 选项来完成的,但是在某些情况下,你可能想在应用程序已经运行的时候添加或删除路由
添加路由
动态路由主要通过两个函数实现。router.addRoute() 和 router.removeRoute()。它们只注册一个新的路由,也就是说,如果新增加的路由与当前位置相匹配,就需要你用 router.push() 或 router.replace() 来手动导航,才能显示该新路由。
router.addRoute({ path: '/about', component: About })
// 我们也可以使用 this.$route 或 route = useRoute() (在 setup 中)
router.replace(router.currentRoute.value.fullPath)
//router.currentRoute相当于$route。但是$route.path或者 $route.fullPath只能在组件中用,js模块中要使用 router.currentRoute
在导航守卫中添加路由
如果你决定在导航守卫内部添加或删除路由,你不应该调用 router.replace(),而是通过返回新的位置来触发重定向:
router.beforeEach(to => {
if (!hasNecessaryRoute(to)) {
router.addRoute(generateRoute(to))
// 触发重定向
return to.fullPath
}
})
删除路由
有几个不同的方法来删除现有的路由:
-
通过添加一个名称冲突的路由。如果添加与现有途径名称相同的途径,会先删除路由,再添加路由:
router.addRoute({ path: '/about', name: 'about', component: About }) // 这将会删除之前已经添加的路由,因为他们具有相同的名字且名字必须是唯一的 router.addRoute({ path: '/other', name: 'about', component: Other }) -
通过调用
router.addRoute()返回的回调:const removeRoute = router.addRoute(routeRecord) removeRoute() // 删除路由如果存在的话当路由没有名称时,这很有用。
-
通过使用
router.removeRoute()按名称删除路由:router.addRoute({ path: '/about', name: 'about', component: About }) // 删除路由 router.removeRoute('about')
添加嵌套路由
要将嵌套路由添加到现有的路由中,可以将路由的 name 作为第一个参数传递给 router.addRoute(),这将有效地添加路由,就像通过 children 添加的一样:
router.addRoute({ name: 'admin', path: '/admin', component: Admin })
router.addRoute('admin', { path: 'settings', component: AdminSettings })
这等效于:
router.addRoute({
name: 'admin',
path: '/admin',
component: Admin,
children: [{ path: 'settings', component: AdminSettings }],
})
查看现有路由
Vue Router 提供了两个功能来查看现有的路由:
router.hasRoute():检查路由是否存在。router.getRoutes():获取一个包含所有路由记录的数组。