所有前端必看!路由守卫看似简单,却藏着很多坑------未登录能直接访问个人中心、管理员页面普通人能进、跳转时数据未加载就渲染。全程实操干货+完整封装,Vue2/Vue3、React 都能用,复制就能实现权限管控
先搞懂:路由守卫到底用来做什么?
不管是 Vue Router 还是 React Router,路由守卫的核心作用只有一个:控制路由的访问权限和跳转逻辑,解决以下高频问题:
- 未登录用户,禁止访问个人中心、订单页等需要权限的页面;
- 不同角色(普通用户/管理员),展示不同的路由页面;
- 页面跳转前,校验数据、确认操作(比如未保存的表单,提示用户);
- 页面加载前,获取必要数据(比如用户信息),避免页面空白。
重点:路由守卫是前端权限管控的核心。Vue 和 React 用法略有差异,但逻辑一致。本文分别给出完整示例,复制就能适配自己的项目,不用再从零编写。
核心干货:Vue2/Vue3 路由守卫完整封装(直接复制)
Vue 项目用 Vue Router,路由守卫分为 3 类:全局守卫、路由独享守卫、组件内守卫。重点掌握全局守卫,就能解决 80% 的权限问题。
1. Vue3 + Vue Router 4(最常用,推荐)
新建 router/index.js,全局守卫 + 路由配置,一步到位:
javascript
// router/index.js(Vue3)
import { createRouter, createWebHistory } from 'vue-router';
import { getStorage } from '@/utils/storage'; // 复用之前封装的 LocalStorage 工具
// 1. 定义路由(区分公开路由和需要权限的路由)
const routes = [
// 公开路由(无需登录就能访问)
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue'),
meta: { requiresAuth: false } // 标记:无需权限
},
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue'),
meta: { requiresAuth: false }
},
// 需要权限的路由(必须登录才能访问)
{
path: '/user',
name: 'UserCenter',
component: () => import('@/views/UserCenter.vue'),
meta: {
requiresAuth: true, // 标记:需要权限
role: 'user' // 角色限制:普通用户即可访问
}
},
// 管理员路由(只有管理员能访问)
{
path: '/admin',
name: 'Admin',
component: () => import('@/views/Admin.vue'),
meta: {
requiresAuth: true,
role: 'admin' // 角色限制:仅管理员
}
},
// 404页面
{
path: '/:pathMatch(.*)*',
name: '404',
component: () => import('@/views/404.vue')
}
];
// 2. 创建路由实例
const router = createRouter({
history: createWebHistory(import.meta.env.VITE_BASE_URL),
routes
});
// 3. 全局前置守卫(跳转前校验,核心)
router.beforeEach((to, from, next) => {
// 1. 获取 token(从 LocalStorage 中取)
const token = getStorage('token');
// 2. 获取当前用户角色(登录后存储的用户信息)
const userRole = getStorage('userInfo')?.role || '';
// 3. 校验逻辑
if (to.meta.requiresAuth) {
// 3.1 需要权限的路由:判断是否登录
if (!token) {
// 未登录,跳转到登录页,登录后返回当前页面
return next({ name: 'Login', query: { redirect: to.fullPath } });
} else {
// 已登录,判断角色是否匹配
if (to.meta.role && to.meta.role !== userRole) {
// 角色不匹配,跳转到首页(或 403 页面)
return next({ name: 'Home' });
}
// 登录且角色匹配,允许跳转
next();
}
} else {
// 3.2 公开路由:直接跳转
next();
}
});
// 4. 全局后置守卫(跳转后执行,比如修改页面标题)
router.afterEach((to) => {
// 设置页面标题
document.title = to.meta.title || '前端路由守卫示例';
});
export default router;
页面中使用(Vue3):
js
<!-- 登录页面,登录成功后跳转回之前的页面 -->
<script setup>
import { useRouter, useRoute } from 'vue-router';
import { setStorage } from '@/utils/storage';
const router = useRouter();
const route = useRoute();
const login = async () => {
const res = await loginApi(); // 登录接口
// 存储 token 和用户信息
setStorage('token', res.data.token, 86400);
setStorage('userInfo', res.data.user, 86400);
// 跳转回之前的页面(如果有),否则跳首页
const redirect = route.query.redirect || '/';
router.push(redirect);
};
</script>
2. Vue2 + Vue Router 3(兼容旧项目)
逻辑和 Vue3 一致,仅语法略有差异,直接复制:
javascript
// router/index.js(Vue2)
import Vue from 'vue';
import Router from 'vue-router';
import { getStorage } from '@/utils/storage';
Vue.use(Router);
const routes = [
// 路由配置和 Vue3 一致
{ path: '/login', name: 'Login', component: () => import('@/views/Login'), meta: { requiresAuth: false } },
{ path: '/user', name: 'UserCenter', component: () => import('@/views/UserCenter'), meta: { requiresAuth: true, role: 'user' } },
{ path: '/admin', name: 'Admin', component: () => import('@/views/Admin'), meta: { requiresAuth: true, role: 'admin' } },
];
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes
});
// 全局前置守卫
router.beforeEach((to, from, next) => {
const token = getStorage('token');
const userRole = getStorage('userInfo')?.role || '';
if (to.meta.requiresAuth) {
if (!token) {
next({ name: 'Login', query: { redirect: to.fullPath } });
} else {
if (to.meta.role && to.meta.role !== userRole) {
next({ name: 'Home' });
} else {
next();
}
}
} else {
next();
}
});
export default router;
核心干货:React + React Router 6 路由守卫封装(直接复制)
React Router 6 取消了传统的路由守卫 API,改用「组件封装」的方式实现权限控制,更灵活,适配 React 函数式组件,直接复制就能用。
1. 封装权限守卫组件(utils/PrivateRoute.js)
jsx
import { Navigate, Outlet } from 'react-router-dom';
import { getStorage } from '@/utils/storage';
/**
* 权限守卫组件
* @param {Object} props - 传入的角色限制
* @param {string} props.role - 允许访问的角色(可选)
*/
export const PrivateRoute = ({ role }) => {
// 获取 token 和用户角色
const token = getStorage('token');
const userRole = getStorage('userInfo')?.role || '';
// 未登录,跳转到登录页
if (!token) {
return <Navigate to="/login" replace />;
}
// 有角色限制,且当前角色不匹配,跳转到首页
if (role && role !== userRole) {
return <Navigate to="/" replace />;
}
// 权限通过,渲染子路由(Outlet 对应 Vue 的 router-view)
return <Outlet />;
};
2. 路由配置(router/index.jsx)
jsx
import { createBrowserRouter } from 'react-router-dom';
import PrivateRoute from '@/utils/PrivateRoute';
// 引入页面组件
import Login from '@/views/Login';
import Home from '@/views/Home';
import UserCenter from '@/views/UserCenter';
import Admin from '@/views/Admin';
import NotFound from '@/views/404';
// 创建路由
const router = createBrowserRouter([
{
path: '/',
element: <Home />
},
{
path: '/login',
element: <Login />
},
// 需要权限的路由:用 PrivateRoute 包裹
{
path: '/user',
element: <PrivateRoute role="user" />, // 普通用户可访问
children: [
{ path: '', element: <UserCenter /> } // 子路由,对应 Outlet
]
},
// 管理员路由:限制角色为 admin
{
path: '/admin',
element: <PrivateRoute role="admin" />,
children: [
{ path: '', element: <Admin /> }
]
},
// 404 页面
{
path: '*',
element: <NotFound />
}
]);
export default router;
3. 入口文件中使用(main.jsx)
jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { RouterProvider } from 'react-router-dom';
import router from './router';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
4.React 登录页面使用:
jsx
import { useNavigate, useLocation } from 'react-router-dom';
import { setStorage } from '@/utils/storage';
function Login() {
const navigate = useNavigate();
const location = useLocation();
// 获取跳转前的页面地址
const redirect = new URLSearchParams(location.search).get('redirect') || '/';
const login = async () => {
const res = await loginApi();
setStorage('token', res.data.token, 86400);
setStorage('userInfo', res.data.user, 86400);
// 跳转回之前的页面
navigate(redirect, { replace: true });
};
return (
<button onClick={login}>登录</button>
);
}
export default Login;
实战避坑:4 个高频坑,新手必避
坑 1:Vue 路由守卫中,忘记调用 next(),导致页面卡死
错误示例 :在 beforeEach 中只做了判断,没调用 next(),路由无法跳转,页面卡死。
正确做法 :所有分支都必须调用 next(),允许跳转用 next(),重定向用 next({ name: 'Login' })。
坑 2:React Router 6 中,用旧版本语法写守卫,导致失效
React Router 6 取消了 beforeEach、afterEach 等 API,不要再用旧版本的写法。
正确做法 :用「PrivateRoute 组件 + Outlet」的方式实现权限控制,本文示例直接可用。
坑 3:未处理"登录后跳转回原页面",体验变差
用户未登录访问需要权限的页面,登录后应该跳转回之前的页面,而不是默认首页。
正确做法 :跳转登录页时,携带当前页面地址(query 参数),登录成功后跳转回去。
坑 4:角色权限判断不严谨,导致越权访问
只判断是否登录,不判断角色,导致普通用户能访问管理员页面。
正确做法 :在路由 meta(Vue)或 PrivateRoute 组件(React)中添加角色限制,登录后校验角色。
进阶技巧:路由守卫高级用法
1. 表单未保存,禁止跳转(组件内守卫 / Vue 专属)
js
<script setup>
import { onBeforeRouteLeave } from 'vue-router';
// 组件内守卫:离开当前页面时触发
onBeforeRouteLeave((to, from, next) => {
// 判断表单是否未保存
if (formIsDirty.value) {
if (confirm('表单未保存,确定要离开吗?')) {
next(); // 确认离开
} else {
next(false); // 取消离开
}
} else {
next(); // 表单已保存,允许离开
}
});
</script>
2. 路由跳转时,加载 loading 状态(全局守卫)
javascript
// Vue3 全局守卫中添加 loading
import { ref } from 'vue';
export const isLoading = ref(false);
router.beforeEach((to, from, next) => {
isLoading.value = true; // 跳转前显示 loading
// 原有校验逻辑...
next();
});
router.afterEach(() => {
setTimeout(() => {
isLoading.value = false; // 跳转后隐藏 loading
}, 300);
});
结尾:干货总结
路由守卫是前端权限管控的核心。Vue 和 React 用法虽有差异,但逻辑一致------判断登录状态、校验角色、控制跳转。一套封装就能覆盖所有场景,避开 4 个高频坑,复制就能实现权限管控。
各位互联网搭子,要是这篇文章成功引起了你的注意,别犹豫,关注、点赞、评论、分享走一波,让我们把这份默契延续下去,一起在知识的海洋里乘风破浪!