Vue-Router 第十节:导航守卫
Vue-Router 第十节:导航守卫
导航守卫是 Vue Router 提供的一种机制,用于在路由发生改变时执行一些特定的逻辑。它们允许你在路由发生变化前、变化后或路由组件内部执行一些操作,比如检查用户是否登录、获取数据、验证路由参数等。
一、全局前置守卫 (beforeEach)
1、概念
全局前置守卫 beforeEach 在路由改变之前被调用。
// 创建路由实例
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeEach((to, from, next) => {
// 处理逻辑
})
2、参数
-
to :即将进入的路由对象
-
from:当前导航正要离开的路由
-
next : 一个函数,必须调用该方法来解析这个钩子,它用于决定导航的下一步行为。
next()详解:
next()
:当调用next()
而不带任何参数时,导航将继续进行到下一个守卫。如果当前守卫是最后一个守卫(即没有后续的守卫或组件内守卫),那么导航将确认并触发组件的更新。next(false)
:调用next(false)
将中断当前的导航。如果浏览器的 URL 改变了(通常是由于用户手动点击了一个链接),那么 URL 将会被重置到from
路由对应的地址。next('/path')
或next({ path: '/path' })
:调用next
并传入一个路径字符串或路由对象将导航到一个新的路由。这类似于用户点击了一个链接到该路径的<router-link>
。当前的导航将被中断,并启动一个新的导航到指定的路径。next(to)
:这与传入路径或路由对象的效果相同,to
是即将进入的目标路由对象。next(error)
:调用next
并传入一个错误对象将导航中断,并把这个错误传递给路由的错误处理函数。
3、使用场景
常用来检查用户是否登录、检查用户权限、加载数据等。
4、案例
检查用户是否登录为案例,本案例十分简单粗糙.........但是对我们要学的内容是能体现出来的。
先模拟一个登录界面:
// 页面login.vue
<template>
<div class="login">
<el-card style="max-width: 480px">
<el-form :inline="true" :model="formInline" class="demo-form-inline">
<el-form-item label="账号:">
<el-input v-model="formInline.user" placeholder="请输入账号" />
</el-form-item>
<el-form-item label="密码:">
<el-input type='password' v-model="formInline.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item>
<el-button type="primary" style="margin-top: 10px; background-color: cadetblue" @click="onSubmit">登陆</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue'
import { useRouter } from 'vue-router'
const formInline = reactive({
user: '',
password: ''
})
const rouer = useRouter()
const onSubmit = () => {
//******重点1*********登录以后给localStorage添加token; 业务代码、功能代码自己写这里是极简。
localStorage.setItem('token', '1')
rouer.push({path:'/home'}) // 登录成功 跳转home路由
}
</script>
<style scoped>
.login {
width: 300px;
height: 100%;
background-color: azure;
}
.demo-form-inline .el-input {
--el-input-width: 220px;
}
.demo-form-inline .el-select {
--el-select-width: 220px;
}
</style>
配置路由及添加路由前置守卫
// router/index.vue
import path from 'path';
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
// 1、定义路由 每个路由都需要映射到一个组件。
const routes: Array<RouteRecordRaw> = [
// 可直接导入或者引入
{
path: '/',
component: () => import("@/views/login/index.vue"),
},
{
path: '/home',
component: ()=>import("@/views/home/index.vue")
}
]
// 2、创建路由实例并传递routes
const router = createRouter({
history: createWebHistory(),
routes
})
// **************************重点在这请看过来*****************
const whileList = ['/'] // 创建一个白名单存放不受判断的路由,这里是我们的登录页面。
router.beforeEach((to, form, next) => {
// 如果存在白名单里,或者token存在那就说明已经登陆了不用再登录了!
if (whileList.includes(to.path) || localStorage.getItem('token')) {
next() // 正常进行
} else {
next('/') // 跳转至登录界面
}
})
// 导出
export default router
进行测试:现在可以将此网页url复制,关掉当前页,在新的窗口打开,效果是:会直接进入home页面。 此时,再把本地缓存的token清掉,再刷新页面 就会进入登陆页面。
二、全局解析守卫(beforeResolve)
1、概念
解析守卫(beforeResolve)在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。
// 创建路由实例
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeResolve((to, from, next) => {
// 获取数据或执行任何其他操作(如果用户无法进入页面时你希望避免执行的操作)的理想位置。
next()
})
2、参数
- to :即将进入的路由对象
- from:当前导航正要离开的路由
- next : 一个函数,必须调用该方法来解析这个钩子,它用于决定导航的下一步行为。
3、使用场景
(1)用户权限验证
(2)登录检查
(3)全局配置(你可以在守卫中检查当前环境,并根据需要设置全局配置,如API接口的基本URL或请求头等。)
(4)数据获取(在用户访问某个需要加载特定数据的页面之前,全局解析守卫可以用来请求这些数据,并在页面加载时直接使用。这有助于优化用户体验,减少页面加载后的数据获取延迟。)
4、案例
例子与前置案例相似,就不展示啦,可以自己试试。
三、全局后置守卫(afterEach)
1、概念
全局后置守卫afterEach在导航完成后被调用,此时组件已经渲染完成。
// 创建路由实例
const router = createRouter({
history: createWebHistory(),
routes
})
router.afterEach((to, from) => {
// 导航完成后执行的逻辑...
})
2、参数
- to :即将进入的路由对象
- from:当前导航正要离开的路由
注: afterEach不需要传入next()
函数, next
函数也不会改变导航本身。
3、使用场景:
全局后置守卫的使用场景主要包括对跳转后的页面进行一些操作,例如滚动条回调至顶部位置、更新页面title、懒加载结束等。它们特别适用于分析、更改页面标题、声明页面等辅助功能,以及其他需要在路由跳转后执行的操作。
注: 全局后置守卫与全局前置守卫不同,它不会改变导航本身,只是简单地执行一些在路由跳转后的操作。因此,在使用全局后置守卫时,需要清楚其执行时机和使用目的,确保在路由跳转后正确地执行所需的操作。
4、案例
// router/index.vue
import path from 'path';
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
// 1、定义路由 每个路由都需要映射到一个组件。
const routes: Array<RouteRecordRaw> = [
// 可直接导入或者引入
{
path: '/',
component: () => import("@/views/login/index.vue"),
},
{
path: '/home',
component: ()=>import("@/views/home/index.vue")
}
]
// 2、创建路由实例并传递routes
const router = createRouter({
history: createWebHistory(),
routes
})
// **************************重点在这请看过来*****************
router.afterEach((to, form) => {
console.log('后置')
alert('跳转啦')
// 你也可以在这里执行其他需要在路由跳转后进行的操作
// 例如,记录用户行为、发送统计信息等
})
// 导出
export default router
四、路由独享的守卫(beforeEnter)
1、概念
在路由配置文件中定义,只对当前路由生效。即:eforeEnter
守卫 只在进入路由时触发,不会在 params
、query
或 hash
改变时触发。
const routes: Array<RouteRecordRaw> = [
// 可直接导入或者引入
{
path: '/',
component: () => import("@/views/login/index.vue"),
},
{
path: '/home',
component: () => import("@/views/home/index.vue"),
beforeEnter: (to, from, next) => { //*************** 路由独享的守卫
// 逻辑判断...
next()
}
}
]
2、参数
-
to :即将进入的路由对象
-
from:当前导航正要离开的路由
-
next : 一个函数,必须调用该方法来解析这个钩子,它用于决定导航的下一步行为。
注:和全局前置守卫参数一样。
3、使用场景
(1)特定路由的权限验证:在某些应用中,不同的路由可能对应着不同的权限级别。通过路由独享的守卫,开发者可以针对每个路由进行单独的权限验证,确保用户只能访问他们被授权访问的页面。
(2)路由进入前的数据预加载:在进入一个展示用户信息的页面之前,可能需要从服务器获取该用户的详细信息。通过在这个路由的守卫中进行数据请求,可以在页面加载时直接使用这些数据,提高用户体验。
(3)离开路由前的确认或清理操作:用户尝试离开某个路由时,可能需要执行一些确认操作,比如提示用户是否保存了修改的内容。或者,可能需要执行一些清理操作,比如清除该路由相关的本地状态或取消某些异步请求。
4、案例
特定路由的权限验证
// router/index.vue
import About from '@/views/about/index.vue'
import path from 'path';
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
// 1、定义路由 每个路由都需要映射到一个组件。
const routes: Array<RouteRecordRaw> = [
// 可直接导入或者引入
{
path: '/',
component: () => import("@/views/login/index.vue"),
},
{
path: '/home',
component: () => import("@/views/home/index.vue"),
beforeEnter:(to,from,next)=>{ //*****看过来********* home的路由独享守卫
if(to.query.role==='管理员'){ // 在独享守卫里判断登陆用户是否是有‘管理员’角色 有就下一步
next()
}else{
window.confirm('非管理员角色不得进入,请登录管理员账号!') // 没有就不能登录
next(false)
}
}
}
]
// 2、创建路由实例并传递routes
const router = createRouter({
// 路由的模式: vue2:
// vue2 mode history | vue3 createWebHistory
// vue2 mode hash | vue3 createWebHashHistory
// vue2 mode abstact | vue3 createMemoryHistory
history: createWebHistory(),
routes
})
// 导出
export default router
// 页面login.vue
<template>
<div class="login">
<el-card style="max-width: 480px">
<el-form :inline="true" :model="formInline" class="demo-form-inline">
<el-form-item label="账号:">
<el-input v-model="formInline.user" placeholder="请输入账号" />
</el-form-item>
<el-form-item label="密码:">
<el-input type='password' v-model="formInline.password" placeholder="请输入密码" />
</el-form-item>
<el-form-item>
<el-button type="primary" style="margin-top: 10px; background-color: cadetblue" @click="onSubmit">登陆</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script lang="ts" setup>
import { reactive } from 'vue'
import { useRouter } from 'vue-router'
const formInline = reactive({
user: '',
password: '',
role:''
})
const rouer = useRouter()
const onSubmit = () => {
if(formInline.user==='1' && formInline.password==='1'){
formInline.role ='管理员' // 模拟账号密码为 1 1 的用户有管理员角色。
}
console.log('submit!')
localStorage.setItem('token', '1')
rouer.push({path:'/home',query:formInline}) // 跳转路由是加上角色参数 注:query 只接受对象
}
</script>
<style scoped>
.login {
width: 300px;
height: 100%;
background-color: azure;
}
.demo-form-inline .el-input {
--el-input-width: 220px;
}
.demo-form-inline .el-select {
--el-select-width: 220px;
}
</style>
五、组件内的守卫
1、概念
用于在组件内部路由变化时执行特定的逻辑,这些守卫允许开发者在路由跳转前、跳转后以及组件重用时执行相应的操作。
- beforeRouteEnter:在渲染该组件的对应路由被验证前调用(在进入路由之前,组件实例还没有被创建,因此不能访问
this
),可以传一个回调给next来访问组件实例。 - beforeRouteUpdate:在当前路由改变,但是该组件被复用时调用,例如:对于一个带有动态参数的路径
/users/:id
,在/users/1
和/users/2
之间跳转的时候,会渲染同样的组件,因此组件实例会被复用,在这个情况下被调用,此时组件已经挂载好了,可以访问组件实例this
。 - beforeRouteLeave: 在导航离开渲染该组件的对应路由时调用,可以访问组件实例
this
。
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
2、参数
-
to :即将进入的路由对象
-
from:当前导航正要离开的路由
-
next : 一个函数,必须调用该方法来解析这个钩子,它用于决定导航的下一步行为。
注:和全局前置守卫参数一样。
3、使用场景
beforeRouteEnter:可以通过这个守卫进行诸如权限验证、数据预加载等操作。例如,可以在进入页面之前异步获取数据。
beforeRouteUpdate :在这里根据新的路由参数更新组件状态。
beforeRouteLeave:这个守卫常用于提示用户是否保存了更改,或者执行一些清理工作,如取消未完成的异步请求或清除组件内的本地状态
4、案例
以 beforeRouteLeave 为例:提示用户是否保存了更改。
// router/index.vue
import About from '@/views/about/index.vue'
import path from 'path';
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
// 1、定义路由 每个路由都需要映射到一个组件。
const routes: Array<RouteRecordRaw> = [
// 可直接导入或者引入
{
path: '/',
component: () => import("@/views/login/index.vue"),
},
{
path: '/home',
component: () => import("@/views/home/index.vue")
}
]
// 2、创建路由实例并传递routes
const router = createRouter({
// 路由的模式: vue2:
// vue2 mode history | vue3 createWebHistory
// vue2 mode hash | vue3 createWebHashHistory
// vue2 mode abstact | vue3 createMemoryHistory
history: createWebHistory(),
routes
})
// 导出
export default router
// home/index.vue
<template>
<div>
<H1>
我是Home页面
</H1>
<div>
<label>姓名:</label> <input style="height: 45px; width: 80%;" type="text" placeholder="请输入名字" v-model="name">
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import { onBeforeRouteLeave } from 'vue-router' // vue3使用setup语法会需要用onBeforeRouteLeave 函数
const name =ref<string>('')
const Changes = ref<boolean>(false)
watch(name, () => { // 监听name改变
if (name) {
Changes.value = true
}
})
onBeforeRouteLeave((to, from, next) => {
if(Changes.value){ // 如果改变了就去提示
const answer = window.confirm('您有未保存的更改,确定要离开吗?');
if (answer) {
next()
} else {
next(false)
}
}else{ // 否则正常进入
next()
}
})
</script>
<style></style>
六、完整导航解析流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave
守卫(导航离开渲染该组件路由时)。 - 调用全局的
beforeEach
守卫(全局前置守卫)。 - 在重用的组件里调用
beforeRouteUpdate
守卫(可以获取组件实例,通常用于组件复用时更新数据)。 - 在路由配置里调用
beforeEnter
守卫(路由独享的守卫)。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
守卫(在渲染该组件的对应路由被验证前调用,此时无法取到组件实例,因为该守卫会在导航确认前被调用,即将登场的新组件还没被创建,它是支持给next()
方法传递回调函数的唯一守卫)。 - 调用全局的
beforeResolve
守卫(全局解析守卫)。 - 导航被确认。
- 调用全局的
afterEach
钩子(全局后置守卫)。 - 触发DOM更新。
- 用创建好的实例调用
beforeRouteEnter
守卫中传给next
的回调函数。
转载自:https://juejin.cn/post/7363301791118639145