导航守卫与懒加载
Vue Router 4 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
导航守卫是路由的「中间件」,负责在跳转发生前后做检查与拦截。这一节掌握全局守卫、路由独享守卫、组件内守卫的先后顺序,写出一个可靠的鉴权流程,并用懒加载控制首屏体积。
问题场景
一个后台项目要求未登录用户访问 /admin 时跳到 /login,登录后回到原地址。第一版把判断写在 AdminLayout 的 onMounted 里——页面先闪一下后台再跳登录,体验糟糕;而且回到原地址的逻辑丢了。守卫就是为「跳转前判断」设计的。
核心解释
守卫按触发顺序分三类:
- 全局前置
router.beforeEach((to, from) => ...):每次跳转前执行,最常用的鉴权点。 - 路由独享
beforeEnter:只在进入某条路由时执行。 - 组件内
beforeRouteEnter/beforeRouteUpdate/beforeRouteLeave:与组件生命周期绑定。
返回值约定:return false 取消导航;return "/login"(或路由对象)重定向;return true/undefined 放行。
// router/index.js
import { createRouter, createWebHistory } from "vue-router";
import { useAuthStore } from "../stores/auth";
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: "/login", name: "login", component: () => import("../views/LoginView.vue") },
{
path: "/admin",
component: () => import("../layouts/AdminLayout.vue"),
meta: { requiresAuth: true },
children: [
{ path: "", name: "admin-home", component: () => import("../views/admin/Dashboard.vue") },
{ path: "users", name: "admin-users", component: () => import("../views/admin/Users.vue") },
],
},
],
});
router.beforeEach((to) => {
const auth = useAuthStore();
if (to.meta.requiresAuth && !auth.isLoggedIn) {
return { name: "login", query: { redirect: to.fullPath } };
}
// 已登录还去登录页:送回后台
if (to.name === "login" && auth.isLoggedIn) {
return { name: "admin-home" };
}
});
登录页读取 route.query.redirect,登录成功后 router.replace(redirect)——用 replace 而不是 push,避免用户后退时又回到登录页。
懒加载:component: () => import("../views/CourseView.vue") 让每条路由单独成块(chunk),首屏只加载当前页需要的代码,Vite 会自动按动态 import 切分。
配合 <Suspense> 或路由的加载态(Vue Router 4 没有内置 loading,通常在 beforeEach 里控制全局进度条,或让组件自己处理 loading)。
守卫里的异步:守卫可以是 async 函数,await 完成后再返回值。但要注意:守卫在导航期间执行,耗时过长会让用户感觉「点了没反应」,超过 1 秒建议加全局进度提示。
组件内守卫的用途:beforeRouteLeave 适合「有未保存修改时拦截离开」:
<script setup>
import { onBeforeRouteLeave } from "vue-router";
onBeforeRouteLeave(() => {
const dirty = /* 是否有未保存修改 */ false;
if (dirty && !confirm("有未保存的修改,确定离开?")) {
return false;
}
});
</script>
示例拆解
一个完整的「登录 → 重定向 → 登出」流程:
<!-- views/LoginView.vue -->
<script setup>
import { ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useAuthStore } from "../stores/auth";
const route = useRoute();
const router = useRouter();
const auth = useAuthStore();
const username = ref("");
const error = ref("");
async function submit() {
error.value = "";
const ok = await auth.login(username.value);
if (!ok) {
error.value = "用户名不能为空";
return;
}
const redirect = String(route.query.redirect ?? "/admin");
await router.replace(redirect);
}
</script>
<template>
<form @submit.prevent="submit">
<input v-model="username" placeholder="用户名" />
<button type="submit">登录</button>
<p v-if="error">{{ error }}</p>
</form>
</template>
// stores/auth.js(Pinia,V11 详讲)
import { defineStore } from "pinia";
import { computed, ref } from "vue";
export const useAuthStore = defineStore("auth", () => {
const user = ref(null);
const isLoggedIn = computed(() => !!user.value);
async function login(name) {
if (!name.trim()) return false;
user.value = { name: name.trim() };
localStorage.setItem("auth.user", JSON.stringify(user.value));
return true;
}
function logout() {
user.value = null;
localStorage.removeItem("auth.user");
}
return { user, isLoggedIn, login, logout };
});
验证路径:未登录访问 /admin/users → 跳到 /login?redirect=/admin/users → 登录 → 回到 /admin/users。刷新后仍是登录态(localStorage 恢复),登出后再访问又跳登录。
动手任务
- 实现上面的鉴权流程,验证 redirect 参数在登录后正确回跳。
- 给
beforeEach加「页面标题」逻辑:根据to.meta.title设置document.title。 - 用
onBeforeRouteLeave给一个「有未保存修改」的表单加离开拦截,验证「取消」后停留在原页面。
常见错误
- 在守卫里用
useAuthStore()但 Pinia 还没安装:守卫在app.use(pinia)之前执行会报错。确保守卫里调用时 Pinia 已挂载(在app.use(router)之前app.use(pinia))。 return false忘记返回:守卫里if (cond) false;不生效,必须return false。- 所有路由都懒加载导致首屏请求瀑布:首屏关键页面可以直接 import,次级页面再懒加载。
- 登录后
push回原页:用户点后退又回到登录页。用replace。
检查与复盘
- 能写出带 redirect 回跳的全局守卫
- 能说出三类守卫的执行顺序与适用场景
- 能解释路由懒加载如何影响首屏体积
参考资料
- Vue Router 官方文档「导航守卫」:完整顺序与返回值
- Vue Router 官方文档「路由懒加载」:动态 import 的用法
读完这一课了?
标记完成后,工作台会安排接下来的学习。