React 路由守衛

下面,我們來系統的梳理關于 React Router 路由守衛 的基本知識點:


一、路由守衛概述

1.1 什么是路由守衛

路由守衛是一種在用戶導航到特定路由之前或離開特定路由時執行邏輯的機制。它允許開發者控制用戶訪問權限、驗證條件或執行數據預加載等操作。

1.2 為什么需要路由守衛

  • 訪問控制:限制未授權用戶訪問敏感頁面
  • 數據預加載:在路由渲染前獲取必要數據
  • 表單保護:防止用戶意外離開包含未保存數據的頁面
  • 權限驗證:根據用戶角色顯示不同內容
  • SEO優化:確保頁面渲染前滿足SEO要求

二、核心守衛類型

2.1 進入守衛(Before Enter)

在路由渲染前執行的守衛邏輯:

// 使用自定義守衛組件
function AuthGuard({ children }) {const { isAuthenticated } = useAuth();const navigate = useNavigate();const location = useLocation();useEffect(() => {if (!isAuthenticated) {navigate('/login', {state: { from: location },replace: true});}}, [isAuthenticated, navigate, location]);return isAuthenticated ? children : <LoadingSpinner />;
}// 在路由配置中使用
<Route path="/dashboard" element={<AuthGuard><Dashboard /></AuthGuard>}
/>

2.2 離開守衛(Before Leave)

在用戶離開當前路由前執行的守衛邏輯:

function UnsavedChangesGuard() {const { isDirty } = useForm();const navigate = useNavigate();useEffect(() => {const handleBeforeUnload = (e) => {if (isDirty) {e.preventDefault();e.returnValue = '';}};window.addEventListener('beforeunload', handleBeforeUnload);return () => {window.removeEventListener('beforeunload', handleBeforeUnload);};}, [isDirty]);useBlocker((tx) => {if (isDirty && !window.confirm('您有未保存的更改,確定要離開嗎?')) {tx.retry();}}, isDirty);return null;
}// 在需要保護的組件中使用
function EditProfile() {return (<><UnsavedChangesGuard />{/* 表單內容 */}</>);
}

2.3 數據加載守衛(Data Loading)

在路由渲染前加載必要數據:

function DataLoader({ children, loader }) {const [data, setData] = useState(null);const [loading, setLoading] = useState(true);const [error, setError] = useState(null);const navigate = useNavigate();useEffect(() => {const fetchData = async () => {try {const result = await loader();setData(result);} catch (err) {setError(err);navigate('/error', { state: { error: err.message } });} finally {setLoading(false);}};fetchData();}, [loader, navigate]);if (loading) return <LoadingSpinner />;if (error) return null; // 已重定向到錯誤頁return children(data);
}// 使用示例
<Route path="/user/:id" element={<DataLoader loader={() => fetchUser(userId)}>{(user) => <UserProfile user={user} />}</DataLoader>}
/>

三、實現路由守衛的四種模式

3.1 高階組件模式(HOC)

function withGuard(Component, guard) {return function GuardedComponent(props) {const navigate = useNavigate();const location = useLocation();useEffect(() => {const result = guard({ location, navigate });if (result?.redirect) {navigate(result.redirect, {replace: true,state: { from: location }});}}, [navigate, location]);return <Component {...props} />;};
}// 定義守衛函數
const authGuard = ({ location }) => {const { isAuthenticated } = useAuth();return !isAuthenticated ? { redirect: `/login?from=${location.pathname}` } : null;
};// 應用守衛
const GuardedDashboard = withGuard(Dashboard, authGuard);

3.2 路由包裝器模式

function RouteGuard({ children, conditions }) {const navigate = useNavigate();const location = useLocation();useEffect(() => {for (const condition of conditions) {const result = condition({ location, navigate });if (result?.redirect) {navigate(result.redirect, {replace: true,state: { from: location }});return;}}}, [conditions, navigate, location]);return children;
}// 使用示例
<Route path="/admin" element={<RouteGuard conditions={[authGuard, adminGuard]}><AdminPanel /></RouteGuard>}
/>

3.3 React Router v6.4+ Loader 模式

const router = createBrowserRouter([{path: '/dashboard',element: <Dashboard />,loader: async () => {const isAuthenticated = await checkAuth();if (!isAuthenticated) {throw redirect('/login');}const data = await fetchDashboardData();return data;},errorElement: <ErrorPage />}
]);// 在組件中使用加載的數據
function Dashboard() {const data = useLoaderData();// 渲染數據...
}

3.4 路由配置元數據模式

const routes = [{path: '/',element: <Home />,public: true},{path: '/profile',element: <Profile />,guards: [authGuard]},{path: '/admin',element: <Admin />,guards: [authGuard, adminGuard]}
];function GuardedRoutes() {return (<Routes>{routes.map((route) => (<Routekey={route.path}path={route.path}element={<GuardProvider guards={route.guards || []}>{route.element}</GuardProvider>}/>))}</Routes>);
}function GuardProvider({ children, guards }) {const navigate = useNavigate();const location = useLocation();useEffect(() => {for (const guard of guards) {const result = guard({ location, navigate });if (result?.redirect) {navigate(result.redirect, {replace: true,state: { from: location }});return;}}}, [guards, navigate, location]);return children;
}

四、常見守衛場景實現

4.1 認證守衛

function useAuthGuard(options = {}) {const { loginPath = '/login' } = options;const { isAuthenticated, isLoading } = useAuth();const navigate = useNavigate();const location = useLocation();useEffect(() => {if (!isLoading && !isAuthenticated) {navigate(loginPath, {replace: true,state: { from: location }});}}, [isAuthenticated, isLoading, navigate, location, loginPath]);return { isAuthenticated, isLoading };
}// 使用示例
function AuthGuard({ children }) {const { isLoading } = useAuthGuard();if (isLoading) return <LoadingSpinner />;return children;
}

4.2 角色權限守衛

function RoleGuard({ children, requiredRoles }) {const { user } = useAuth();const navigate = useNavigate();const location = useLocation();useEffect(() => {if (user && !hasRequiredRoles(user.roles, requiredRoles)) {navigate('/forbidden', {replace: true,state: { from: location }});}}, [user, requiredRoles, navigate, location]);if (!user) return <LoadingSpinner />;return hasRequiredRoles(user.roles, requiredRoles) ? children : null;
}// 輔助函數
function hasRequiredRoles(userRoles, requiredRoles) {return requiredRoles.some(role => userRoles.includes(role));
}// 使用示例
<Route path="/admin" element={<RoleGuard requiredRoles={['admin', 'superadmin']}><AdminPanel /></RoleGuard>}
/>

4.3 訂閱狀態守衛

function SubscriptionGuard({ children }) {const { subscription } = useUser();const navigate = useNavigate();useEffect(() => {if (subscription?.status === 'expired') {navigate('/renew-subscription', { replace: true });} else if (!subscription?.isActive) {navigate('/pricing', { replace: true });}}, [subscription, navigate]);return subscription?.isActive ? children : <LoadingSpinner />;
}

4.4 功能開關守衛

function FeatureGuard({ children, feature }) {const { isFeatureEnabled } = useFeatureFlags();const navigate = useNavigate();useEffect(() => {if (!isFeatureEnabled(feature)) {navigate('/feature-disabled', { replace: true });}}, [feature, isFeatureEnabled, navigate]);return isFeatureEnabled(feature) ? children : null;
}

五、高級守衛模式

5.1 組合守衛

function composeGuards(...guards) {return function CombinedGuard({ location, navigate }) {for (const guard of guards) {const result = guard({ location, navigate });if (result) return result;}return null;};
}// 創建組合守衛
const adminAreaGuard = composeGuards(authGuard,adminGuard,subscriptionGuard
);// 使用組合守衛
<Route path="/admin" element={<RouteGuard guard={adminAreaGuard}><AdminPanel /></RouteGuard>}
/>

5.2 異步守衛

function AsyncGuard({ children, guard }) {const [isAllowed, setIsAllowed] = useState(null);const navigate = useNavigate();const location = useLocation();useEffect(() => {let isMounted = true;const checkGuard = async () => {try {const result = await guard({ location, navigate });if (isMounted) {if (result?.redirect) {navigate(result.redirect, {replace: true,state: { from: location }});} else {setIsAllowed(true);}}} catch (error) {if (isMounted) {navigate('/error', {state: { error: error.message },replace: true});}}};checkGuard();return () => {isMounted = false;};}, [guard, navigate, location]);if (isAllowed === null) return <LoadingSpinner />;return isAllowed ? children : null;
}// 使用示例
const asyncAuthGuard = async () => {const isAuth = await checkAuthToken();return isAuth ? null : { redirect: '/login' };
};<Route path="/dashboard" element={<AsyncGuard guard={asyncAuthGuard}><Dashboard /></AsyncGuard>}
/>

5.3 條件重定向守衛

function ConditionalRedirect({ children, condition, to }) {const navigate = useNavigate();const location = useLocation();useEffect(() => {if (condition()) {navigate(to, {replace: true,state: { from: location }});}}, [condition, to, navigate, location]);return condition() ? null : children;
}// 使用示例
<Route path="/profile" element={<ConditionalRedirect condition={() => isMobile()} to="/mobile/profile"><DesktopProfile /></ConditionalRedirect>}
/>

六、實踐與常見問題

6.1 路由守衛最佳實踐

  1. 守衛順序:先認證后權限,先通用后特殊
  2. 加載狀態:異步檢查時提供加載指示器
  3. 錯誤處理:妥善處理守衛中的錯誤
  4. 避免循環:確保守衛邏輯不會導致無限重定向
  5. 測試覆蓋:為守衛編寫單元測試和集成測試

6.2 常見問題解決方案

問題:無限重定向循環

// 登錄頁面守衛
function LoginPage() {const { isAuthenticated } = useAuth();const navigate = useNavigate();const location = useLocation();useEffect(() => {if (isAuthenticated) {// 檢查來源是否已經是登錄頁const from = location.state?.from?.pathname || '/';if (from !== '/login') {navigate(from, { replace: true });} else {navigate('/', { replace: true });}}}, [isAuthenticated, navigate, location]);// 渲染登錄表單...
}

問題:守衛多次觸發

function StableGuard({ children, guard }) {const navigate = useNavigate();const location = useLocation();const initialCheck = useRef(false);useEffect(() => {if (!initialCheck.current) {initialCheck.current = true;const result = guard({ location, navigate });if (result?.redirect) {navigate(result.redirect, {replace: true,state: { from: location }});}}}, [guard, navigate, location]);return children;
}

問題:組件卸載時導航

function SafeEffectGuard({ children, guard }) {const navigate = useNavigate();const location = useLocation();const isMounted = useRef(true);useEffect(() => {return () => {isMounted.current = false;};}, []);useEffect(() => {const result = guard({ location, navigate });if (result?.redirect && isMounted.current) {navigate(result.redirect, {replace: true,state: { from: location }});}}, [guard, navigate, location]);return children;
}

七、案例:電商平臺路由守衛

// 路由配置
const router = createBrowserRouter([{path: '/',element: <MainLayout />,children: [{index: true,element: <HomePage />},{path: 'product/:id',element: <ProductDetailPage />,loader: async ({ params }) => {const product = await fetchProduct(params.id);if (!product) throw new Error('Product not found');return product;},errorElement: <ProductErrorPage />},{path: 'cart',element: <CartPage />,guards: [authGuard]},{path: 'checkout',element: <CheckoutPage />,guards: [authGuard, cartNotEmptyGuard],loader: async () => {const [cart, addresses] = await Promise.all([fetchCart(),fetchAddresses()]);return { cart, addresses };}},{path: 'admin',element: <AdminLayout />,guards: [authGuard, adminGuard],children: [{index: true,element: <AdminDashboard />},{path: 'products',element: <ProductManagement />}]}]},{path: '/login',element: <LoginPage />},{path: '*',element: <NotFoundPage />}
]);// 購物車非空守衛
const cartNotEmptyGuard = ({ navigate }) => {const { cartItems } = useCart();if (cartItems.length === 0) {navigate('/cart', {state: { message: '請先添加商品到購物車' },replace: true});return { block: true };}return null;
};// 管理員守衛
const adminGuard = ({ navigate }) => {const { user } = useAuth();if (!user?.roles.includes('admin')) {navigate('/forbidden', { replace: true });return { block: true };}return null;
};

八、總結

8.1 路由守衛關鍵點

  1. 守衛類型:進入守衛、離開守衛、數據守衛
  2. 實現模式:高階組件、路由包裝器、Loader API、元數據配置
  3. 常見場景:認證、權限、訂閱狀態、功能開關
  4. 高級模式:守衛組合、異步守衛、條件重定向

8.2 性能優化建議

  • 守衛復用:創建可復用的守衛組件
  • 按需加載:使用React.lazy進行代碼分割
  • 緩存策略:緩存守衛檢查結果
  • 取消請求:組件卸載時取消異步守衛操作
  • 并行加載:使用Promise.all并行執行多個守衛

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/91320.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/91320.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/91320.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

7月31日作業

1&#xff1a;請使用函數模板&#xff0c;寫一個能夠針對所有數據類型的數據的快速排序函數 并多寫幾個數組做測試代碼#include <iostream> #include <cstring> #include <cstdlib> #include <unistd.h> #include <sstream> #include <vector…

客戶服務自動化:如何用CRM減少50%人工工單?

通過CRM系統實現客戶服務自動化&#xff0c;企業可以顯著減少人工工單的數量&#xff0c;提升整體服務效率。那么如何利用CRM系統實現客戶服務自動化&#xff1f;幫助企業從根本上解決人工工單處理的難題&#xff0c;提升服務質量&#xff0c;優化資源配置&#xff0c;最終實現…

常用設計模式系列(十四)—模板方法模式

常用設計模式系列&#xff08;十四&#xff09;—模板方法模式 第一節 前言 之前我完成了創建型設計模式和結構型設計模式&#xff0c;我們今天將踏入設計模式的第三章&#xff1a;行為型設計模式&#xff0c;你是否還記得什么是行為型設計模式嗎&#xff1f;行為型模式&#x…

DoRA詳解:從LoRA到權重分解的進化

DoRA??是一種用于??大語言模型&#xff08;LLM&#xff09;微調??的技術&#xff0c;全稱為 ??"Weight-Decomposed Low-Rank Adaptation"??&#xff08;權重分解的低秩自適應&#xff09;。它是對現有微調方法&#xff08;如 ??LoRA??&#xff09;的改…

RocksDB關鍵設計詳解

0 說明 近日工作中使用了 RocksDB。RocksDB 的優點此處無需多說&#xff0c;它的一個 feature 是其有很多優化選項用于對 RocksDB 進行調優。欲熟悉這些參數&#xff0c;必須對其背后的原理有所了解&#xff0c;本文主要整理一些 RocksDB 的 wiki 文檔&#xff0c;以備自己參考…

Kotlin -> 普通Lambda vs 掛起Lambda

1. 普通Lambda vs 掛起Lambda的本質區別 1.1 普通Lambda&#xff08;同步執行&#xff09; val lambda: (Int) -> String { it.toString() }// 編譯器生成&#xff1a; class Lambda$1 : Function1<Int, String> {override fun invoke(p1: Int): String {return p1.t…

Apache Ignite 中如何配置和啟用各類監控指標

這段文檔是關于 Apache Ignite 中如何配置和啟用各類監控指標&#xff08;Metrics&#xff09; 的詳細說明。核心思想是&#xff1a;“指標收集有性能開銷&#xff0c;因此默認不開啟所有指標&#xff0c;需要你按需手動開啟。” 下面我們來逐層拆解、通俗易懂地理解這些內容。…

uniapp x swiper/image組件mode=“aspectFit“ 圖片有的閃現后黑屏

部分安卓機針對大寫.JPG 有的豎圖正常&#xff0c;橫圖/正方形不對。解決方案&#xff1a;加border-radius: 1rpx;就行<!-- 圖片預覽彈出框 --><fui-backdrop v-model:visible"imgPreviewVisible" :closable"true" onclick"imgPreviewVisibl…

conda安裝jupter

conda自帶的jupter本來在base里沒有在pytorch環境中 安裝jupter conda install nb_conda 此擴展程序在 Jupyter 文件瀏覽器中添加了一個 Conda 選項卡。選擇 Conda 選項卡將顯示&#xff1a; 當前存在的 Conda 環境列表當前配置的通道中可用的 Conda 包列表&#xff08;htt…

嵌入式操作系統快速入門(1):快速入門操作系統常見基礎概念

快速體會操作系統常見基礎概念 1 初識基本概念 1.1 操作系統 一個軟件程序&#xff1b;用于解決計算機多任務執行時的資源爭搶問題&#xff1b;管理計算機中的各種資源&#xff0c;確保計算機正常完成各種工作&#xff08;任務&#xff09;&#xff0c;解決多任務環境中任務的調…

網絡安全-同形異義字攻擊:眼見并非為實(附案例詳解)

什么是同形異義字攻擊&#xff1f;對人眼而言&#xff0c;一切看起來完全正常。但實際上&#xff0c;例如單詞 Ηоmоgraph 并不完全等同于單詞 Homograph。它們之間的差異非常細微&#xff0c;難以察覺。Ηоmоgraph 實際上包含了幾個非拉丁字母。在本例中&#xff0c;我們將…

windows服務器 maven 配置環境變量,驗證maven環境變量是否配置成功

前置條件&#xff1a;先確認對應版本的jdk已安裝配置好&#xff0c;可使用java -version檢測; 我使用的apache-maven-3.6.3是對應jdk1.8 1.找到系統變量配置窗口 以windows server2019為例&#xff0c;右鍵計算機屬性&#xff0c; 高級系統設置–》環境變量–》系統變量2.新建M…

安裝 docker compose v2版 筆記250731

安裝 docker compose v2版 筆記250731 簡述 v2版是插件形式 確認系統要求, 已安裝 Docker Engine&#xff08;版本 20.10.5 或更高&#xff09; 安裝方式可分為 apt 或 yum 安裝 (能自動升級) apt install docker-compose-pluginyum install docker-compose-plugin 手動二…

PHP 5.5 Action Management with Parameters (English Version)

PHP 5.5 Action Management with Parameters (English Version) Here’s a PHP 5.5 compatible script that uses URL parameters instead of paths for all operations: <?php // Start session for persistent storage session_start();// Initialize the stored actio…

GR-3(4B) 技術報告--2025.7.23--字節跳動 Seed

0. 前言 前兩天字節發布了GR-3&#xff0c;粗略的看了一下&#xff0c;在某些方面超過了SOTA pi0&#xff0c;雖然不開源&#xff0c;但是也可以來看一看。 官方項目頁 1. GR-3模型 1.1 背景 在機器人研究領域&#xff0c;一直以來的目標就是打造能夠幫助人類完成日常任務…

Linux網絡編程:UDP 的echo server

目錄 前言&#xff1a; 一、服務端的實現 1、創建socket套接字 2、綁定地址信息 3、執行啟動程序 二、用戶端的實現 總結&#xff1a; 前言&#xff1a; 大家好啊&#xff0c;前面我們介紹了一些在網絡編程中的一些基本的概念知識。 今天我們就借著上節課提到的&#…

AI+金融,如何跨越大模型和場景鴻溝?

文&#xff5c;白 鴿編&#xff5c;王一粟當AI大模型已開始走向千行百業之時&#xff0c;備受看好的金融行業&#xff0c;卻似乎陷入了落地瓶頸。打開手機銀行想查下貸款額度&#xff0c;對著屏幕說了半天&#xff0c;AI客服卻只回復 “請點擊首頁貸款按鈕”&#xff1b;客戶經…

深度解析:從零構建跨平臺對象樹管理系統(YongYong框架——QT對象樹機制的現代化替代方案)

一、技術背景與核心價值 1.1 QT對象樹的局限性 在Qt框架中&#xff0c;QObject通過對象樹機制實現了革命性的對象管理&#xff1a; #mermaid-svg-SvqKmpFjg76R02oL {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-Sv…

力扣46:全排列

力扣46:全排列題目思路代碼題目 給定一個不含重復數字的數組 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意順序 返回答案。 思路 看到所有可能首先想到的就是回溯。 回溯的結束條件也很好寫&#xff0c;用數組的長度來判斷即可。這道題的難點主要是如何進行判…

mac環境配置rust

rustup 是一個命令行工具&#xff0c;用于管理 Rust 編譯器和相關工具鏈 sh 體驗AI代碼助手 代碼解讀復制代碼curl --proto ‘https’ --tlsv1.2 -sSf https://sh.rustup.rs | sh使得 Rust 的安裝在當前 shell 環境中生效 如果你使用的是 bash, zsh 或其他類似的 shell&#xf…