导航守卫
所谓导航守卫,就是在当你进行导航的时候将其拦截下来,从而方便做一些其他的事情。
快速上手
js
// 全局导航守卫
router.beforeEach((to, from, next) => {
// 回调函数里面决定了拦截下来后做什么
console.log("from:", from);
console.log("to:", to);
console.log("导航到:", to.name);
next(); // 调用该方法代表放行
});
这是一个全局导航守卫,回调会自动传入 3 个参数:
- to:即将要进入的目标路由,是一个对象,对象里面有 path、fullPath、hash、params 等参数
- from:当前导航正要离开的路由,同样是一个对象,对象内部有上述参数
- next:是一个函数,表示导航放行
各种拦截守卫
整体分为 3 大类:
全局守卫
beforeEach:全局前置守卫,会在解析组件守卫和异步路由组件之前被调用
beforeResolve:全局解析守卫,在导航被确认之前,但在所有组件内守卫和异步路由组件被解析之后调用
- 上面两个其实就是执行的时机一前一后
afterEach:全局后置守卫,在导航确认后触发的钩子函数。该钩子函数执行后会触发 DOM 更新,用户看到新的页面。
- 思考 🤔:既然导航都已经确认了,这里安插一个守卫干嘛呢?
- 全局后置守卫经常用于如下的场景:
- 记录页面访问历史:可以使用 afterEach 来记录用户访问的页面,以便进行统计或分析。
- 关闭加载指示器:在 beforeEach 中开启加载指示器,在 afterEach 中关闭它,以提供更好的用户体验。
- 页面切换动画:在 afterEach 中触发页面切换动画或其他视觉效果,以提升用户体验。
- 更新文档标题:在导航完成后更新页面标题,以反映当前页面内容。
js//全局拦截--路由进入前 /* 一般进行权限控制,比如登录权限,权限等级,权限跳转等 */ router.beforeEach((to, from, next) => { console.log("全局路由守卫--进入前"); console.log(from); console.log(to); next(); }); // 全局拦截--路由解析后 router.beforeResolve((to, from, next) => { console.log("全局路由守卫--解析后"); console.log(from); console.log(to); next(); }); // 全局拦截--路由进入后 router.afterEach((to, from) => { console.log("全局路由守卫--进入后"); console.log(from); console.log(to); });
路由守卫
路由守卫 beforeEnter:针对特定路由设置的守卫,因此设置的方式也不再是在 router 路由实例上面设置,而是在某个路由配置中设置。
js
const routes = [
{
path: '/users/:id',
component: UserDetails,
// 在路由的配置中进行设置,只针对特定的路由进行拦截
beforeEnter: (to, from,next) => {
next()
},
},
]
相关细节:
- beforeEnter 守卫只在进入路由时触发,不会在 params、query 或 hash 改变时触发。
- 从 /users/2 进入到 /users/3 这种不会触发
- 从 /users/2#info 进入到 /users/2#projects 这种也不会触发
- 如果放在父级路由上,路由在具有相同父级的子路由之间移动时,它不会被触发。
js
const routes = [
{
path: "/user",
beforeEnter() {
// ...
},
children: [
{ path: "list", component: UserList },
{ path: "details", component: UserDetails },
],
},
];
从 /user/list 跳转到 /user/details 不会触发路由级别守卫。
组件守卫
组件守卫:这种守卫是组件级别,取决于是否进入或者离开某个组件
- onBeforeRouteUpdate:当前路由改变,但是该组件被复用时调用,例如对于一个带有动态参数的路径 /users/:id,在 /users/1 和 /users/2 之间跳转的时候会触发
- onBeforeRouteLeave:离开了该导航,组件失活的时候
整体的执行顺序:
- 组件离开守卫
- 全局前置守卫
- 路由级别守卫
- 全局解析守卫
- 全局后置守卫
- 组件进入守卫
如果是组件复用,参数变化的情况,执行顺序如下:
- 全局前置守卫
- 组件更新守卫
- 全局解析守卫
- 全局后置守卫
vue
<script setup>
import { onBeforeRouteUpdate, onBeforeRouteLeave } from "vue-router";
//组件内守卫只有更新和离开
onBeforeRouteUpdate((to, from) => {
// /detail/:id 当id变化,可以监听到
console.log("组件内守卫--更新", to, from);
});
onBeforeRouteLeave((to, from) => {
console.log("组件内守卫--离开", to, from);
});
</script>
其他细节
1. 路由级别守卫 beforeEnter 设置多个值
路由级别守卫,也就是 beforeEnter 可以设置成一个数组,数组里面包含多个方法,每个方法进行一项处理。
js
const routes = [
// ...
{
path: "/about",
name: "About",
component: About,
beforeEnter: [
(to, from, next) => {
console.log("Route beforeEnter step 1");
next();
},
(to, from, next) => {
console.log("Route beforeEnter step 2");
next();
},
],
},
// ...
];
2. 在守卫内的全局注入
从 Vue 3.3 开始,你可以在导航守卫内使用 inject() 方法。这在注入像 pinia stores 这样的全局属性时很有用。
在 app.provide() 中提供的所有内容都可以在全局守卫里面获取到。
js
// main.js
const app = createApp();
app.provide("global", "some data");
js
// router.js
import { inject } from "vue";
router.beforeEach(() => {
const data = inject("global");
const userStore = useUserStore();
});
实战案例
导航守卫拦截拦截登录
使用导航守卫拦截未登录的用户,将未登录用户导航到登录页面。
已登录,但是不是管理员,在点击管理员页面的时候,跳转到首页。
角色:普通用户、管理员
页面:主页、用户页、管理员页、登录
未登录:主页、登录
用户身份登录:主页、用户页、登录
管理员身份登录:主页、用户页、管理员页、登录
js
//模拟状态管理存储的用户信息
let currentRole = null;
export function login(role) {
currentRole = role;
localStorage.setItem("role", role);
}
export function logout() {
currentRole = null;
localStorage.removeItem("role");
}
export function getCurrentRole() {
return currentRole;
}
vue
<template>
<div class="login-container">
<template v-if="currentRole">
<p>
您当前的身份为: <strong>{{ userRole }}</strong
>.
</p>
<button @click="logoutHandle">退出登录</button>
</template>
<template v-else>
<h2>登录</h2>
<form @submit.prevent="loginHandle">
<input type="text" placeholder="请输入用户名" v-model="username" />
<input type="password" placeholder="请输入密码" v-model="password" />
<button type="submit">登录</button>
</form>
</template>
</div>
</template>
<script setup>
import { ref, computed } from "vue";
import { getCurrentRole, login, logout } from "@/store/user";
const username = ref("");
const password = ref("");
const currentRole = ref(localStorage.getItem("role") || "");
const userRole = computed(() => {
if (currentRole.value === "admin") {
return "管理员";
} else if (currentRole.value === "user") {
return "普通用户";
} else {
return null;
}
});
const loginHandle = () => {
if (username.value === "admin" && password.value === "admin") {
login("admin"); //设置角色为管理员
currentRole.value = getCurrentRole();
} else {
alert("用户名或密码错误");
}
};
const logoutHandle = () => {
logout();
currentRole.value = "";
};
</script>
<style scoped>
.login-container {
max-width: 400px;
margin: 100px auto;
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
text-align: center;
}
input {
display: block;
width: calc(100% - 24px);
margin: 10px auto;
padding: 10px;
border: 1px solid #ced4da;
border-radius: 4px;
}
button {
padding: 10px 20px;
border: none;
border-radius: 4px;
background-color: #007bff;
color: #fff;
cursor: pointer;
}
</style>
js
import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/Home.vue";
import Login from "../views/Login.vue";
import User from "../views/User.vue";
import Admin from "../views/Admin.vue";
import { getCurrentRole } from "@/store/user";
const routes = [
{ path: "/", name: "Home", component: Home },
{ path: "/login", name: "Login", component: Login },
// 仅仅需要普通权限即可
{
path: "/user",
name: "User",
component: User,
meta: {
auth: true, //需要登陆鉴权
},
},
{
path: "/admin",
name: "Admin",
component: Admin,
meta: {
auth: true, //需要登陆鉴权
requireAdmin: true, //需要管理员权限
},
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
// 全局路由守卫--上面需求的逻辑在这里编写
router.beforeEach((to, from, next) => {
if (to.meta.auth) {
const role = getCurrentRole();
if (!role) {
next("/login");
} else if (to.meta.requireAdmin && role !== "admin") {
next("/");
} else {
next();
}
} else {
next();
}
});
export default router;
--
-EOF-