React Router V7 本身并没有直接提供内置的路由守卫 API(比如 Vue Router 的 beforeEach),但通过组合其提供的钩子(如 useNavigate、useLocation)和 React 的组件设计模式,我们可以实现类似的功能,比如权限控制、登录验证、数据预加载等场景。
路由守卫
路由守卫是指在路由切换时执行一些逻辑,以决定是否允许导航到目标路由。
常见的场景包括:
- 权限控制:只有特定角色的用户才能访问某些页面。
- 登录验证:未登录用户被重定向到登录页。
- 数据预加载:在进入页面前加载必要的数据。
:::color5
在 React Router V7 中,我们可以通过以下方式实现:
- 高阶组件(HOC) 或 自定义组件:封装路由逻辑。
- useEffect + useNavigate:在组件内部检查条件并重定向。
- 全局守卫组件:在路由层统一处理。
:::
自定义组件 - 路由守卫
集中化管理路由
- 定义路由配置数组,包含路径、组件和是否需要认证的标志。
- 集中管理路由,便于扩展和维护。
这里可以自由扩展,例如组件的图标,标题等,方便在菜单使用。
// 路由配置
const routeConfig = [
{ path: "/", element: <Home />, requiresAuth: true },
{ path: "/article", element: <Article />, requiresAuth: true },
{ path: "/person", element: <Person />, requiresAuth: true },
{ path: "/login", element: <Login />, requiresAuth: false },
];
自定义组件
isAuthenticated
检测是否登录,如果未登录则跳转登录页
RouterGuard
- 使用 useNavigate 和 useLocation 监听路由变化。
- 在 useEffect 中实现全局守卫逻辑:如果路由需要认证且用户未登录,则重定向到 /login。
- replace: true 确保重定向不会保留历史记录。
const isAuthenticated = () => {
return localStorage.getItem("token") !== null; // 示例逻辑
};
// 全局路由组件
function RouterGuard() {
const navigate = useNavigate();
const location = useLocation();
useEffect(() => {
const currentRoute = routeConfig.find(
(route) => route.path === location.pathname
);
// 全局导航守卫逻辑
if (currentRoute?.requiresAuth && !isAuthenticated()) {
console.log("未登录,重定向到登录页");
navigate("/login", { replace: true });
}
}, [location, navigate]); // 监听路由变化
return (
<Routes>
{routeConfig.map((route) => (
<Route
key={route.path}
path={route.path}
element={route.element}
/>
))}
</Routes>
);
}
export default RouterGuard;
在入口应用路由
import RouterGuard from './routes/RouterGuard';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import './App.css'
function App() {
return (
<BrowserRouter>
<RouterGuard />
</BrowserRouter>
);
}
export default App
高阶组件
- <font style="color:rgb(36, 41, 47);">路由分层结构</font><font style="color:rgb(36, 41, 47);">:使用</font>
<font style="color:rgb(36, 41, 47);"><BrowserRouter></font>
<font style="color:rgb(36, 41, 47);">包裹应用根组件</font> - <font style="color:rgb(36, 41, 47);">集中式配置</font><font style="color:rgb(36, 41, 47);">:通过</font>
<font style="color:rgb(36, 41, 47);">createBrowserRouter</font>
<font style="color:rgb(36, 41, 47);">创建路由实例</font> <font style="color:rgb(36, 41, 47);">全局守卫</font><font style="color:rgb(36, 41, 47);">:</font>
- <font style="color:rgb(36, 41, 47);">使用路由对象的</font>
<font style="color:rgb(36, 41, 47);">loader</font>
<font style="color:rgb(36, 41, 47);">属性处理前置守卫</font> - <font style="color:rgb(36, 41, 47);">使用</font>
<font style="color:rgb(36, 41, 47);">errorElement</font>
<font style="color:rgb(36, 41, 47);">处理后置守卫</font>
- <font style="color:rgb(36, 41, 47);">使用路由对象的</font>
- <font style="color:rgb(36, 41, 47);">鉴权路由</font><font style="color:rgb(36, 41, 47);">:创建高阶组件包裹受保护路由</font>
- <font style="color:rgb(36, 41, 47);">动态加载</font><font style="color:rgb(36, 41, 47);">:通过</font>
<font style="color:rgb(36, 41, 47);">lazy()</font>
<font style="color:rgb(36, 41, 47);">实现代码分割</font>
// 1. 创建路由实例
import {
createBrowserRouter,
RouterProvider,
redirect
} from "react-router-dom";
// 鉴权高阶组件
const AuthCheck = ({ children }) => {
const isLogin = localStorage.getItem("token");
return isLogin ? children : redirect("/login");
};
// 路由配置
const router = createBrowserRouter([
{
path: "/",
// 全局前置守卫(所有页面都会触发)
loader: async () => {
console.log("Global before hook");
return null;
},
// 全局错误处理
errorElement: <ErrorPage />,
element: <Layout />,
children: [
{
index: true,
element: <Home />
},
{
path: "profile",
// 路由级前置守卫
loader: async () => {
if (!localStorage.getItem("token")) {
return redirect("/login");
}
return null;
},
element: <Profile />
},
{
path: "admin",
element: (
<AuthCheck>
<AdminPage />
</AuthCheck>
)
}
]
},
{
path: "/login",
element: <Login />,
// 路由加载器(前置处理)
loader: async () => {
if (localStorage.getItem("token")) {
return redirect("/");
}
return null;
}
}
]);
// 应用入口
function App() {
return <RouterProvider router={router} />;
}
<font style="color:rgb(36, 41, 47);">核心差异总结</font>
- <font style="color:rgb(36, 41, 47);">实现方式</font><font style="color:rgb(36, 41, 47);">:</font>
- <font style="color:rgb(36, 41, 47);">React:组件化思维,通过路由配置对象和HOC组合实现</font>
- <font style="color:rgb(36, 41, 47);">Vue:基于选项式API,提供完整导航守卫方法</font>
- <font style="color:rgb(36, 41, 47);">执行顺序</font><font style="color:rgb(36, 41, 47);">:</font>
- <font style="color:rgb(36, 41, 47);">React:loader执行 → 组件渲染</font>
- <font style="color:rgb(36, 41, 47);">Vue:全局守卫 → 路由守卫 → 组件守卫</font>
- <font style="color:rgb(36, 41, 47);">动态加载</font><font style="color:rgb(36, 41, 47);">:</font>
- <font style="color:rgb(36, 41, 47);">React:通过</font>
<font style="color:rgb(36, 41, 47);">lazy()</font>
<font style="color:rgb(36, 41, 47);"> </font><font style="color:rgb(36, 41, 47);">+</font><font style="color:rgb(36, 41, 47);"> </font><font style="color:rgb(36, 41, 47);"><Suspense></font>
<font style="color:rgb(36, 41, 47);">原生支持</font> - <font style="color:rgb(36, 41, 47);">Vue:需要配合异步组件实现</font>
- <font style="color:rgb(36, 41, 47);">类型系统</font><font style="color:rgb(36, 41, 47);">:</font>
- <font style="color:rgb(36, 41, 47);">React Router v6提供完整的TS类型定义</font>
- <font style="color:rgb(36, 41, 47);">Vue Router需要手动扩展RouteMeta类型</font>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。