react19【实战】配置化路由登录鉴权完整方案

React19 + React Router v6 完整配置化路由鉴权全套代码

整体目录结构

复制代码
src
├── main.jsx                      # 项目入口
├── App.jsx                       # 根组件
├── context
│   └── AuthContext.jsx           # 全局登录状态 + useAuth
├── components
│   └── RequireAuth.jsx           # 权限守卫组件
├── router
│   ├── index.js                  # 统一导出路由配置
│   ├── routes.js                 # 配置化路由数组(核心)
│   └── RouterRender.jsx          # 递归渲染路由工具组件
└── pages
    ├── Login.jsx
    ├── Home.jsx
    ├── Profile.jsx
    ├── Admin.jsx
    └── NotFound.jsx

1. src/context/AuthContext.jsx(登录状态)

js 复制代码
import { createContext, useContext, useState, useEffect } from 'react'

const AuthContext = createContext(null)

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(true)

  // 页面初始化读取本地存储,保持登录
  useEffect(() => {
    const token = localStorage.getItem('token')
    const userStr = localStorage.getItem('user')
    if (token && userStr) {
      setUser(JSON.parse(userStr))
    }
    setLoading(false)
  }, [])

  // 登录方法
  const login = (token, userInfo) => {
    localStorage.setItem('token', token)
    localStorage.setItem('user', JSON.stringify(userInfo))
    setUser(userInfo)
  }

  // 退出登录
  const logout = () => {
    localStorage.removeItem('token')
    localStorage.removeItem('user')
    setUser(null)
  }

  const store = {
    user,
    loading,
    login,
    logout
  }

  return (
    <AuthContext.Provider value={store}>
      {children}
    </AuthContext.Provider>
  )
}

// 自定义鉴权钩子
export function useAuth() {
  const ctx = useContext(AuthContext)
  if (!ctx) throw new Error('useAuth 必须在 AuthProvider 内部调用')
  return ctx
}

2. src/components/RequireAuth.jsx(权限拦截守卫)

js 复制代码
import { Navigate, Outlet, useLocation } from 'react-router-dom'
import { useAuth } from '../context/AuthContext'

/**
 * 路由鉴权守卫
 * @param {boolean} needAdmin 是否需要管理员权限
 */
export default function RequireAuth({ needAdmin = false }) {
  const { user, loading } = useAuth()
  const location = useLocation()

  if (loading) return <div>权限校验加载中...</div>

  // 未登录,跳转登录页,记录原本访问地址
  if (!user) {
    return (
      <Navigate
        to="/login"
        state={{ from: location }}
        replace
      />
    )
  }

  // 需要管理员,但用户不是管理员
  if (needAdmin && user.role !== 'admin') {
    return <Navigate to="*" replace />
  }

  // 校验通过,渲染页面
  return <Outlet />
}

3. src/router/routes.js(路由配置数组,统一管理)

js 复制代码
import Login from '../pages/Login'
import Home from '../pages/Home'
import Profile from '../pages/Profile'
import Admin from '../pages/Admin'
import NotFound from '../pages/NotFound'

/**
 * 路由配置项说明
 * path 路由地址
 * element 页面组件
 * meta 路由元信息
 * meta.isPublic = true 公开页面,无需登录
 * meta.requiresAuth = true 需要登录
 * meta.needAdmin = true 需要管理员权限
 * children 嵌套子路由
 */
export const routes = [
  {
    path: '/login',
    element: <Login />,
    meta: {
      title: '登录',
      isPublic: true
    }
  },
  {
    path: '/',
    element: <Home />,
    meta: {
      title: '首页',
      requiresAuth: true
    }
  },
  {
    path: '/profile',
    element: <Profile />,
    meta: {
      title: '个人中心',
      requiresAuth: true
    }
  },
  {
    path: '/admin',
    element: <Admin />,
    meta: {
      title: '管理员后台',
      requiresAuth: true,
      needAdmin: true
    }
  },
  {
    path: '*',
    element: <NotFound />,
    meta: {
      title: '404页面',
      isPublic: true
    }
  }
]

4. src/router/RouterRender.jsx(递归渲染路由)

自动根据 meta 元信息判断是否包裹权限守卫,支持嵌套路由

js 复制代码
import { Route } from 'react-router-dom'
import RequireAuth from '../components/RequireAuth'

export default function RouterRender({ routes }) {
  return routes.map(routeItem => {
    const { path, element, meta, children } = routeItem

    // 公开页面,不做权限拦截
    if (meta.isPublic) {
      return (
        <Route key={path} path={path} element={element}>
          {children && RouterRender({ routes: children })}
        </Route>
      )
    }

    // 需要登录鉴权的路由,外层套守卫
    return (
      <Route
        key={path}
        path={path}
        element={<RequireAuth needAdmin={meta.needAdmin} />}
      >
        <Route index element={element} />
        {children && RouterRender({ routes: children })}
      </Route>
    )
  })
}

5. src/router/index.js(统一导出入口)

js 复制代码
export { routes } from './routes'
export { default as RouterRender } from './RouterRender'

6. src/App.jsx(根组件,极简配置式渲染)

js 复制代码
import { Routes } from 'react-router-dom'
import { AuthProvider } from './context/AuthContext'
import { routes, RouterRender } from './router'

export default function App() {
  return (
    {/* 全局登录状态注入 */}
    <AuthProvider>
      <Routes>
        {/* 一行渲染全部配置化路由 */}
        <RouterRender routes={routes} />
      </Routes>
    </AuthProvider>
  )
}

7. src/main.jsx(React19 标准入口)

js 复制代码
import { createRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import App from './App'

createRoot(document.getElementById('root')).render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
)

8. 页面组件代码

pages/Login.jsx

js 复制代码
import { useNavigate, useLocation } from 'react-router-dom'
import { useAuth } from '../context/AuthContext'

export default function Login() {
  const navigate = useNavigate()
  const location = useLocation()
  const { login } = useAuth()

  // 获取登录后要跳转的页面
  const targetPath = location.state?.from?.pathname || '/'

  // 普通用户登录
  const loginUser = () => {
    const token = 'user-token-001'
    const userInfo = {
      id: 1001,
      name: '普通用户',
      role: 'user'
    }
    login(token, userInfo)
    navigate(targetPath, { replace: true })
  }

  // 管理员登录
  const loginAdmin = () => {
    const token = 'admin-token-999'
    const userInfo = {
      id: 9999,
      name: '超级管理员',
      role: 'admin'
    }
    login(token, userInfo)
    navigate(targetPath, { replace: true })
  }

  return (
    <div style={{ padding: '100px 30px' }}>
      <h2>系统登录</h2>
      <button onClick={loginUser} style={{ marginRight: 10, padding: '6px 12px' }}>
        普通用户登录
      </button>
      <button onClick={loginAdmin} style={{ padding: '6px 12px' }}>
        管理员登录
      </button>
    </div>
  )
}

pages/Home.jsx

js 复制代码
import { useAuth } from '../context/AuthContext'
import { useNavigate } from 'react-router-dom'

export default function Home() {
  const { user, logout } = useAuth()
  const navigate = useNavigate()

  const signOut = () => {
    logout()
    navigate('/login', { replace: true })
  }

  return (
    <div style={{ padding: '30px' }}>
      <h2>首页</h2>
      <p>欢迎您,{user.name}</p>
      <p>账号身份:{user.role === 'admin' ? '管理员' : '普通用户'}</p>

      <div style={{ margin: '20px 0' }}>
        <button onClick={() => navigate('/profile')} style={{ marginRight: 8 }}>
          个人中心
        </button>
        <button onClick={() => navigate('/admin')}>
          管理员后台
        </button>
      </div>

      <button onClick={signOut}>退出登录</button>
    </div>
  )
}

pages/Profile.jsx

js 复制代码
import { useAuth } from '../context/AuthContext'

export default function Profile() {
  const { user } = useAuth()
  return (
    <div style={{ padding: '30px' }}>
      <h2>个人中心</h2>
      <p>用户ID:{user.id}</p>
      <p>用户名:{user.name}</p>
      <p>角色:{user.role}</p>
    </div>
  )
}

pages/Admin.jsx

js 复制代码
export default function Admin() {
  return (
    <div style={{ padding: '30px' }}>
      <h2>管理员后台</h2>
      <p>该页面仅管理员账号可访问</p>
    </div>
  )
}

pages/NotFound.jsx

js 复制代码
import { useNavigate } from 'react-router-dom'

export default function NotFound() {
  const navigate = useNavigate()
  return (
    <div style={{ padding: '30px' }}>
      <h2>404 页面不存在</h2>
      <button onClick={() => navigate('/')}>返回首页</button>
    </div>
  )
}

功能特性总结

  1. 完全配置化 :所有路由统一写在 routes.js,新增页面只添加一条配置,不改动 JSX
  2. 分层鉴权
    • 公开页面:登录/404 直接放行
    • 基础鉴权:首页、个人中心(必须登录)
    • 角色鉴权:Admin 页面需要 admin 角色
  3. 登录回跳:未登录访问受保护页面,登录后自动回到目标页面
  4. 持久化登录:localStorage 存储 token、用户信息,刷新页面不失效
  5. 支持嵌套路由:RouterRender 递归渲染,后台多级菜单直接扩展 children
  6. 无重复模板代码,工程化标准写法,便于后续对接后端动态路由
相关推荐
其美杰布-富贵-李1 小时前
Vue Router 实战教程
前端·javascript·vue.js
NutShell Wang1 小时前
拆解 GitHub gh-stack:堆叠 PR 工作流的设计取舍与工程实现
前端·git·开源·github·代码复审·开发者工具·vibe coding
名字还没想好☜1 小时前
React 用 IntersectionObserver 实现图片懒加载与无限滚动:封装一个 useInView Hook
前端·javascript·vue.js·react.js·react
朝阳392 小时前
react19【系列实用教程】setSearchParams
react.js
带娃的IT创业者2 小时前
Puppeteer 深度解析:超越自动化测试的现代 Web 交互范式
前端·交互·puppeteer·可观测性·浏览器自动化·无头浏览器·web工程化
程序员爱钓鱼2 小时前
Rust 所有权 Ownership 详解:理解内存安全的核心机制
前端·后端·rust
Mh10 小时前
别再只会 `find` 了:Map 在前端业务里的真实用法
前端·javascript
陈随易11 小时前
FFmpeg 9.0 发布,代号 Lei,音视频处理再升级
前端·后端·程序员
爱丶狸11 小时前
Grafana_Zabbix_ImageRenderer_部署与前端操作手册
linux·前端·zabbix·grafana·kylin