Vue-Router 路由介绍
前端路由: 它是组件和路径的映射关系
在脚手架搭建的项目中或者引入vue-router并注册之后,vue提供了两个全局组件 RouterView 和 RouterLink, 提供了两个全局对象 $$router和 $route
声明式导航
<router-link to='/my'>本质是a标签</router-link>
注意:router-link 必须传一个 to 属性,属性值必须跟路由规则数组中 component 对应的path 属性值一致。
优点
自带激活时的类名: linkActiveClass 和 linkExactActiveClass
声明式导航传参
@1 查询字符串传参
(参数在url地址上),通过 query 接收值
@@1 字符串拼接传参 只要通过问号 ? 传参的,都在 query 里面。
// 传参方式
<router-link to='/my?name=值1&age=值2&...'>本质是a标签</router-link>
// 获取时 标签内
<p>{{ $route.query.name }}</p>
@@2 配置对象传参
<router-link :to = '{
path: '/my',
query:{
name: "值1",
age: "值2"
}
}'>本质是a标签</router-link>
// 接收参数
<p>{{ $route.query.age }}</p>
@2 动态路由传参
动态路由传参,接收值得时候,看 params
const routes = [
...
{
name: 'my'
path: '/my/:name/:age',
component: MyComponent
}
...
]
动态路由传参
to属性值为字符串时
<router-link to='/my/testnam/testage'>本质是a标签</router-link>
to属性值为配置对象时
<router-link
:to = '{
name: 'my'
params:{
name: "值1",
age: "值2"
}
}'>本质是a标签</router-link>
// 接收参数
<p>{{ $route.params.age }}</p>
注意:
- 动态路由不能通过 path + params 配合,因为 path 会忽略 parmas
- 这里的path 指的是,RouterLink 的 to 属性的属性值不能出现 path
- 正确方式: name(需要给对应的路由取名,也叫命名路由)+params
- 虽然写的也是对象,但是最终会转为字符串
编程式导航
- 不传参
// 字符串方式
this.$router.push('/my')
// 对象方式
this.$router.push({
path: '/my'
})
- 传参
@1 查询字符串传参
// 字符串方式
this.$router.push('/my?name=testname&age=24')
// 对象方式
this.$router.push({
path: '/my',
query: {
name: 'testname',
age: 24
}
})
@2 动态路由传参
// 字符串形式传参
this.$router.push('/my/testname/age')
// 对象方式传参
this.$router.push({
name: 'my',
params:{
name: 'testName',
age: 24
}
})
路由嵌套
二级路由 path 一般不写根路径 ( / )
// 二级路由 path 一般不写根路径 ( / )
const routes = [
{
path: '/my',
component: 'MyComponent',
redirect: '/my/recommend',
// children 配置路由嵌套
// 重定向: 首次加载的显示 redirect 指向的组件
children: [
// 注意: 从二级路由开始,path 不写 / ,
// 但是跳转的时候,二级路由需要用path+ / +自己的path形成一个完整的路径
// 重定向和此方法(完整路径方法)二选一
// 使用完整路径方法,就不需要解path 为 recommend 的配置对象
// {
// path: '',
// component: Recommend
// }
{
path: 'recommend',
component: Recommend
},
{
path: 'pageFirst',
componen: 'PageFirst'
},
{
path: 'pageSecond',
componen: 'PageSecond'
}
]
}
]
Vue-Router 导航守卫
导航守卫主要分为三种:
- 全局守卫 (全局导航守卫)
- 独享路由守卫(独享导航守卫)
- 组件内路由守卫
全局导航守卫
全局导航守卫有三种:
- beforeEach(全局前置守卫)
- beforeResolve(全局解析守卫)
- afterEach(全局后置守卫)
方法使用:
import router from './router'
// router.beforeEach 全局前置守卫 进入路由之前调用
router.beforeEach((to, from, next) => {
next();
});
// router.beforeResolve 全局解析守卫 在beforeRouteEnter(组件内路由守卫)调用之后调用
router.beforeResolve((to, from, next) => {
next();
});
// router.afterEach 全局后置守卫 进入路由之后
router.afterEach((to, from) => {
console.log('afterEach 全局后置钩子');
});
路由导航参数
to: 即将要进入的目标路由对象
from: 当前导航正要离开的路由
next: 该方法用于控制导航的行为,该参数是个函数,且必须调用,否则不能进入路由(页面空白)。 它有下几个用法:
- next():调用next()表示继续导航,即允许用户访问目标路由。
- next('/path'):调用next('path地址')表示重定向导航到指定的路径。这可以用于在导航守卫中进行重定向,例如在未登录时将用户重定向到登录页面。
- next({ path: '/path' }):调用next({ path: '/path' })表示重定向导航到指定的路径。与上一个用法相似,但使用了一个包含路径的对象作为参数。
- next({ name: 'pathname'})
- next(false):调用next(false)表示终止导航,即不允许用户访问目标路由。这通常用于进行权限验证或者登录状态检查,如果条件不满足,则终止导航。
- next(error):调用next(error)表示终止导航并传递一个错误对象。这可以用于在导航守卫中处理错误情况,例如在网络请求失败时中止导航并显示错误信息。
- next(callback):调用next(callback)表示异步导航,其中callback是一个回调函数。这可以用于在导航守卫中执行异步操作,例如在获取数据后再继续导航。
tips:next方法只能在导航守卫函数中调用,并且每个导航守卫函数只调用一次next方法。
全局前置守卫
router.beforeEach()
常用场景:router.beforeEach()在路由切换之前调用,可以用来进行权限验证、登录状态检查等操作。用户如果没有登录,只能访问登录界面,不能访问其他页面。用户如果登录了,就可以访问其他页面,不能访问登录页面。
router.beforeEach((to, from, next) => {
const hasToken = getToken()
if (hasToken) {
if (to.path === '/login') {
next({
path: store.state.permission.rootRoute
})
} else {
let isFirst = store.state.permission.isGetRoutes
store.dispatch('permission/initPage').then(() => {
if (!isFirst && to.path == '/home') {
next({
path: store.state.permission.rootRoute
})
} else {
next()
}
}).catch(() => {
next()
})
next()
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
next(`/login?redirect=${to.path}`)
}
}
})
全局解析守卫
router.beforeResolve()
常用场景:beforeResolve在路由切换之前调用,但在beforeEach 和组件内beforeRouteEnter守卫之后调用,afterEach之前调用。在该守卫中,可以在导航被确认之前执行异步操作或者获取组件数据,可以在所有组件内守卫和异步路由组件被解析之后调用。
router.beforeResolve((to, from, next) => {
// 在这里执行一些异步操作,例如加载数据
getFetchData().then(() => {
next();
});
});
全局后置守卫
router.afterEach
afterEach在路由切换之后调用,在beforeEach和beforeResolve之后,beforeRouteEnter回调之前。 常用场景:可以用来进行页面统计、错误处理等操作,通常用于页面渲染完成后的操作。该守卫没有next参数,因此不能中止导航。
例如我们可以利用全局前置守卫和全局后置守卫制作一个进度条,我们还需要引入第三方库(NProgress)及其样式,这样项目上方会出现进度条。
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import { isRelogin } from '@/utils/request'
NProgress.configure({ showSpinner: false })
const whiteList = ['/login', '/auth-redirect', '/bind', '/register','/casLogin']
const defaultSettings = require('@/settings')
router.beforeEach((to, from, next) => {
NProgress.start()
if (getToken()) {
to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
/* has token*/
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
if (store.getters.roles.length === 0) {
isRelogin.show = true
// 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(() => {
isRelogin.show = false
store.dispatch('GenerateRoutes').then(accessRoutes => {
// 根据roles权限生成可访问的路由表
router.addRoutes(accessRoutes) // 动态添加可访问路由表
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
}).catch(err => {
store.dispatch('LogOut').then(() => {
Message.error(err)
next({ path: '/' })
})
})
} else {
next()
}
}
} else {
// 没有token
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
next()
} else {
// 根据是否启用cas判断需要跳转的登录页地址
// if(defaultSettings.enableCas){
// next(`/casLogin`)
// }else{
next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
// }
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done()
})
路由独享守卫
beforeEnter
路由独享守卫:主要用来判断用户是否有权限访问某个页面,如果没有权限就跳转到首页。路由独享守卫仅对特定的路由生效,可以用于处理特定路由的逻辑。beforeEnter守卫只在进入路由时触发,不会在params、query或hash改变时触发 从/pagedetail/2进入到/pagedetail/3或者从/pagedetail/2#first进入到/pagedetail/2#seconds时,守卫不会触发,只有在从一个不同的路由导航时,才会被触发。
const routes = [
{
path: '/pageHome',
component: PageHome,
beforeEnter: (to, from, next) => {
// 在进入路由之前执行一些操作
if (isLoggedIn()) {
// 用户已登录,允许访问路由
next();
} else {
// 用户未登录,重定向到登录页面
next('/login');
}
}
},
// ...
// 其他路由配置...
]
组件内路由守卫
组件内路由守卫有三种:
- beforeRouteEnter
- beforeRouteUpdate
- beforeRouteLeave
beforeRouteEnter
常用场景:在数据预加载时,在进入组件之前,需要先加载一些数据。就可以使用 beforeRouteEnter, 组件内守卫来调用接口获取数据,并在数据加载完成后再进入组件。
因为钩子在组件实例还没被创建的时候调用,所以不能获取组件实例 this,可以通过传一个回调给 next 来访问组件实例 。 但是回调的执行时机在 mounted 后面,所以在我看来这里对 this 的访问意义不太大,可以放在 created 或者 mounted 里面。
beforeRouteEnter (to, from, next) {
console.log('在路由独享守卫后调用');
next(vm => {
// 通过 `vm` 访问组件实例`this` 执行回调的时机在mounted后面,
})
},
执行顺序: beforeEach 和独享守卫 beforeEnter 之后,全局 beforeResolve 和全局 afterEach 之前调用.
beforeRouteUpdate
beforeRouteUpdate:用于在组件复用时,路由参数发生变化时执行操作,可以通过this访问组件实例,因为组件已经挂载好了
常用场景:路由参数更新----当同一个组件在不同参数下进行切换时,可能需要根据新的参数更新组件的数据或状态。可以使用 beforeRouteUpdate 组件内守卫来处理这种情况。
beforeRouteUpdate 是在当前路由改变,但是该组件被复用时调用
例如对于一个带有动态参数的路径 /users/:id ,在 /users/1 和 /users/2 之间跳转的时候,
由于会渲染同样的 UserDetails 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 this
beforeRouteUpdate(to, from, next) {
if (to.params.id !== from.params.id) {
// 当路由参数 id 发生变化时,重新请求数据
this.fetchData(to.params.id);
}
next();
},
beforeRouteLeave
常用场景:
@1 数据清理:在离开当前路由之前需要执行一些清理操作,例如取消订阅事件、重置组件状态等。可以使用 beforeRouteLeave 组件内守卫来处理这些操作。
beforeRouteLeave(to, from, next) {
if (this.hasUnsavedChanges()) {
if (confirm('是否保存修改的数据?')) {
this.saveData(); // 保存修改的数据
}
}
next();
},
@2 页面切换动画:在切换页面时添加过渡动画效果,以提升用户体验。可以在 beforeRouteEnter 和 beforeRouteLeave 组件内守卫中设置过渡动画的相关逻辑。
beforeRouteEnter(to, from, next) {
// 在进入组件之前设置初始过渡状态
this.transitionName = 'slide-in';
next();
},
beforeRouteLeave(to, from, next) {
// 在离开组件之前设置过渡状态
this.transitionName = 'slide-out';
next();
},
路由守卫执行的完成过程
- beforeRouteLeave: 路由组件的组件离开路由前钩子,可以取消路由离开。
- beforeEach: 路由全局前置守卫,用于登录校验。
- beforeRouteUpdate: 在复用组件内部路由守卫钩子
- beforeEnter: 路由独享守卫
- beforeRouteEnter: 路由组件的组件进入路由前钩子
- beforeResolve: 全局解析守卫
- afterEach: 路由全局后置钩子
- beforeCreate: 不能访问this
- created: 可以访问this,不能访问dom
- beforeMoute
- deactived: 离开缓存组件A,或者触发A的beforeDestory和destroy组件销毁钩子
- mouted
- activated: 进入缓存组件,进入a的嵌套子组件
- 执行beforeRouteEnter回调函数 next