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>