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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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…