FE-Interview中的React Router:路由守卫与权限控制

FE-Interview中的React Router:路由守卫与权限控制

【免费下载链接】FE-Interview 🔥🔥🔥 前端面试,独有前端面试题详解,前端面试刷题必备,1000+前端面试真题,Html、Css、JavaScript、Vue、React、Node、TypeScript、Webpack、算法、网络与安全、浏览器 【免费下载链接】FE-Interview 项目地址: https://gitcode.com/gh_mirrors/fei/FE-Interview

你是否在开发React应用时遇到过用户未登录就能访问需要权限的页面?或者管理员和普通用户看到了相同的菜单?React Router(路由)的守卫功能正是为解决这些问题而生。本文将结合FE-Interview项目中的实战经验,教你如何用路由守卫实现灵活的权限控制,让你的应用更安全、用户体验更专业。读完本文,你将掌握3种路由守卫实现方式、4个权限控制场景的解决方案,以及1套可直接复用的代码模板。

路由守卫基础:从登录验证开始

路由守卫(Route Guard)是前端路由的"守门人",能在用户进入/离开页面时验证权限。在React生态中,这一功能通常通过React Router结合高阶组件或Hooks实现。

基础登录守卫实现

最常见的场景是未登录用户访问需授权页面时自动跳转至登录页。以下是基于React Router v6的实现:

// 登录守卫组件
const PrivateRoute = ({ element }) => {
  const isAuthenticated = localStorage.getItem('token');
  const location = useLocation();
  
  // 未登录则重定向到登录页,并记录当前位置以便登录后返回
  return isAuthenticated ? (
    element
  ) : (
    <Navigate to="/login" state={{ from: location.pathname }} replace />
  );
};

// 路由配置中使用
<Routes>
  <Route path="/login" element={<LoginPage />} />
  <Route path="/dashboard" element={<PrivateRoute element={<Dashboard />} />} />
  <Route path="/profile" element={<PrivateRoute element={<Profile />} />} />
</Routes>

这种方式在FE-Interview项目的React题目中有详细讨论,特别是如何配置React-Router实现路由切换部分提到了类似的保护路由模式。

进阶权限控制:基于角色的访问控制

当应用需要区分管理员、普通用户、访客等不同角色时,基础登录验证已不能满足需求。此时需要实现基于角色的路由控制。

多角色路由守卫

// 带角色检查的路由守卫
const RoleBasedRoute = ({ element, requiredRoles }) => {
  const { user } = useAuth(); // 假设从Auth Context获取用户信息
  const location = useLocation();
  
  if (!user) {
    // 未登录重定向到登录页
    return <Navigate to="/login" state={{ from: location.pathname }} replace />;
  }
  
  if (!requiredRoles.some(role => user.roles.includes(role))) {
    // 角色不匹配,重定向到无权限页面
    return <Navigate to="/unauthorized" replace />;
  }
  
  return element;
};

// 路由配置中使用
<Routes>
  <Route path="/admin" element={
    <RoleBasedRoute 
      element={<AdminPanel />} 
      requiredRoles={['admin']} 
    />
  } />
  <Route path="/editor" element={
    <RoleBasedRoute 
      element={<ContentEditor />} 
      requiredRoles={['admin', 'editor']} 
    />
  } />
  <Route path="/viewer" element={
    <RoleBasedRoute 
      element={<ContentViewer />} 
      requiredRoles={['admin', 'editor', 'viewer']} 
    />
  } />
</Routes>

这种基于角色的权限控制模式在企业级应用中非常常见,FE-Interview项目的React题目汇总中虽然没有直接对应的实现,但React组件通信方式中提到的Context API可以用来传递用户权限信息,这是实现角色控制的基础。

细粒度权限控制:基于功能的访问控制

在复杂应用中,可能需要更细粒度的权限控制,例如控制某个按钮是否显示,或者某个API是否可调用。

功能级权限控制

// 权限检查Hook
const usePermission = (permission) => {
  const { user } = useAuth();
  
  // 检查用户是否有特定权限
  const hasPermission = useMemo(() => {
    return user?.permissions?.includes(permission) || false;
  }, [user, permission]);
  
  return hasPermission;
};

// 权限控制按钮组件
const PermissionButton = ({ permission, ...props }) => {
  const hasPermission = usePermission(permission);
  
  // 无权限则不渲染按钮或禁用
  if (!hasPermission) {
    return props.fallback || null;
  }
  
  return <button {...props} />;
};

// 在组件中使用
const UserManagement = () => {
  const canCreateUser = usePermission('user:create');
  const canDeleteUser = usePermission('user:delete');
  
  return (
    <div>
      <h1>用户管理</h1>
      {canCreateUser && <Button onClick={handleCreateUser}>新增用户</Button>}
      
      <UserTable 
        onDelete={canDeleteUser ? handleDeleteUser : undefined}
        showDeleteButton={canDeleteUser}
      />
      
      {/* 或者使用PermissionButton组件 */}
      <PermissionButton 
        permission="user:export" 
        onClick={handleExport}
        fallback={<span>无导出权限</span>}
      >
        导出用户
      </PermissionButton>
    </div>
  );
};

这种细粒度的权限控制可以与路由守卫结合使用,形成完整的权限控制体系。在FE-Interview项目中,React高阶组件的概念可以用来实现类似的权限包装组件。

路由守卫的高级应用

路由切换时的数据验证

有时需要在进入路由前验证数据是否存在或有效,例如编辑页面需要确保数据已加载:

const DataGuard = ({ element, loadData, dataId }) => {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const fetchData = async () => {
      try {
        await loadData(dataId);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, [loadData, dataId]);
  
  if (loading) return <LoadingSpinner />;
  if (error) return <ErrorPage message="无法加载数据" />;
  
  return element;
};

// 使用示例 - 编辑产品页面
<Route 
  path="/products/:id/edit" 
  element={
    <DataGuard 
      element={<ProductEditor />}
      dataId={useParams().id}
      loadData={fetchProductDetails}
    />
  } 
/>

退出确认守卫

当用户在表单页面有未保存的更改时,离开页面应该给出提示:

const UnsavedChangesGuard = ({ element }) => {
  const location = useLocation();
  const navigate = useNavigate();
  const { hasUnsavedChanges, confirmLeave } = useFormStatus();
  
  useEffect(() => {
    const handleBeforeUnload = (e) => {
      if (hasUnsavedChanges) {
        e.preventDefault();
        e.returnValue = '您有未保存的更改,确定要离开吗?';
        return e.returnValue;
      }
    };
    
    window.addEventListener('beforeunload', handleBeforeUnload);
    
    // React Router导航守卫
    const unblock = navigate(blocker);
    
    return () => {
      window.removeEventListener('beforeunload', handleBeforeUnload);
      unblock();
    };
    
    function blocker({ nextLocation }) {
      if (hasUnsavedChanges && nextLocation.pathname !== location.pathname) {
        if (window.confirm('您有未保存的更改,确定要离开吗?')) {
          return true;
        }
        return false;
      }
      return true;
    }
  }, [hasUnsavedChanges, location, navigate]);
  
  return element;
};

// 使用示例
<Route 
  path="/forms/profile" 
  element={
    <UnsavedChangesGuard element={<ProfileForm />} />
  } 
/>

路由守卫的集中式管理

随着应用规模增长,路由守卫的逻辑会变得复杂。将守卫逻辑集中管理可以提高代码可维护性。

使用路由配置文件集中管理

// 路由配置文件 - routes.js
import { lazy } from 'react';

// 懒加载组件
const Login = lazy(() => import('../pages/Login'));
const Dashboard = lazy(() => import('../pages/Dashboard'));
const AdminPanel = lazy(() => import('../pages/AdminPanel'));
const Profile = lazy(() => import('../pages/Profile'));
const NotFound = lazy(() => import('../pages/NotFound'));

// 路由配置数组
const routes = [
  {
    path: '/login',
    element: <Login />,
    guards: [], // 不需要守卫
    public: true // 公开访问
  },
  {
    path: '/dashboard',
    element: <Dashboard />,
    guards: ['auth'], // 需要登录
  },
  {
    path: '/profile',
    element: <Profile />,
    guards: ['auth'], // 需要登录
  },
  {
    path: '/admin',
    element: <AdminPanel />,
    guards: ['auth', 'role:admin'], // 需要登录和管理员角色
  },
  {
    path: '/*',
    element: <NotFound />,
    guards: [],
    public: true
  }
];

export default routes;

// 路由守卫工厂函数
const createRouteGuard = (guards = []) => {
  return (element) => {
    // 从右向左应用守卫,形成嵌套结构
    return guards.reduceRight((acc, guard) => {
      switch (guard) {
        case 'auth':
          return <AuthGuard element={acc} />;
        case 'role:admin':
          return <RoleGuard element={acc} requiredRoles={['admin']} />;
        case 'role:editor':
          return <RoleGuard element={acc} requiredRoles={['admin', 'editor']} />;
        // 其他守卫类型...
        default:
          return acc;
      }
    }, element);
  };
};

// 路由生成组件
const AppRoutes = () => {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        {routes.map((route, index) => (
          <Route
            key={index}
            path={route.path}
            element={createRouteGuard(route.guards)(route.element)}
          />
        ))}
      </Routes>
    </Suspense>
  );
};

这种集中式路由管理方式在大型应用中尤为重要,它将路由配置和守卫逻辑分离,便于维护和扩展。FE-Interview项目中虽然没有直接提供这种实现,但React高阶组件的概念与此处的守卫包装函数思想一致,都是通过包装组件来增强功能。

总结与最佳实践

路由守卫是React应用中实现权限控制和用户体验优化的关键技术。通过本文介绍的几种守卫类型,你可以构建从简单到复杂的权限控制系统:

  1. 基础登录守卫:控制未登录用户访问
  2. 角色守卫:基于用户角色的访问控制
  3. 功能守卫:细粒度的功能级权限控制
  4. 数据守卫:确保路由切换时数据准备就绪
  5. 退出守卫:防止用户意外丢失未保存的更改

最佳实践建议:

  • 保持守卫逻辑单一职责,一个守卫只处理一种类型的检查
  • 使用高阶组件或自定义Hook封装守卫逻辑,提高复用性
  • 集中管理路由配置,使权限规则一目了然
  • 结合React Context API管理全局权限状态
  • 始终提供清晰的错误提示,帮助用户理解权限限制

更多关于React Router和权限控制的实践,可以参考FE-Interview项目React题目汇总,其中包含了大量前端面试中常见的React路由和权限相关问题及解答。

【免费下载链接】FE-Interview 🔥🔥🔥 前端面试,独有前端面试题详解,前端面试刷题必备,1000+前端面试真题,Html、Css、JavaScript、Vue、React、Node、TypeScript、Webpack、算法、网络与安全、浏览器 【免费下载链接】FE-Interview 项目地址: https://gitcode.com/gh_mirrors/fei/FE-Interview

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值