React Router v6中用Context API实现路由守卫时上下文取值异常问题
问题原因
你遇到的问题是因为**createBrowserRouter在App组件外部提前创建了路由实例**,路由中用到的ProtectedRoute等组件元素在App的AppContext.Provider渲染前就已初始化,导致这些组件无法进入Provider的作用域,只能获取到Context的默认值,而非Provider提供的动态token状态。
解决方案
有两种可行的修复方式,任选其一即可:
方案一:将路由定义移到App组件内部
把createBrowserRouter的创建逻辑放到App组件中,确保路由组件在AppContext.Provider的作用域内初始化:
修改App.js
import { useState } from 'react'; import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import AppContext from './context/AppContext.js'; // 导入所需组件 import Root from './components/Root'; import Home from './components/Home'; import Admin from './components/Admin'; import Login from './components/Login'; import ProtectedRoute from './components/ProtectedRoute'; function App() { const [token, setToken] = useState(""); function login(token) { setToken(token); } // 在App组件内部创建路由实例 const router = createBrowserRouter([ { path: "/", element: <Root />, children: [ { path: "home", element: <Home /> }, { path: "admin", element: <ProtectedRoute element={Admin} /> }, { path: "login", element: <Login /> } ] } ]); return ( <React.StrictMode> <AppContext.Provider value={{ token, login }}> <RouterProvider router={router} /> </AppContext.Provider> </React.StrictMode> ); } export default App;
可选:拆分路由配置
如果不想把路由逻辑全部堆在App.js里,可以把路由配置抽成数组单独存放:
routes.js
// 仅导出路由配置数组,不提前创建router实例 import Root from './components/Root'; import Home from './components/Home'; import Admin from './components/Admin'; import Login from './components/Login'; import ProtectedRoute from './components/ProtectedRoute'; export default [ { path: "/", element: <Root />, children: [ { path: "home", element: <Home /> }, { path: "admin", element: <ProtectedRoute element={Admin} /> }, { path: "login", element: <Login /> } ] } ];
然后在App.js中导入并创建router
import routes from './routes/routes.js'; // ... const router = createBrowserRouter(routes);
方案二:将Context Provider移到路由根组件内
把AppContext.Provider放到路由的根组件Root中,让所有子路由组件自动处于Context作用域:
修改Root.js
import { useState } from 'react'; import { Outlet } from 'react-router-dom'; import AppContext from '../context/AppContext.js'; function Root() { const [token, setToken] = useState(""); function login(token) { setToken(token); } return ( <AppContext.Provider value={{ token, login }}> {/* 这里可以添加根组件的公共布局(比如导航栏) */} <Outlet /> {/* 渲染子路由内容 */} </AppContext.Provider> ); } export default Root;
修改App.js
去掉外层的AppContext.Provider,直接渲染RouterProvider即可:
import router from './routes/routes.js'; function App() { return ( <React.StrictMode> <RouterProvider router={router} /> </React.StrictMode> ); } export default App;
额外优化建议
- 给
ProtectedRoute的Navigate添加replace属性,避免登录后返回登录页的历史记录:
export default function ProtectedRoute({ element: Component }) { const { token } = useContext(AppContext); return token ? <Component /> : <Navigate to="/login" replace />; }
- 确保Login组件正确调用Context中的
login函数:
import { useContext } from 'react'; import AppContext from './context/AppContext.js'; function Login() { const { login } = useContext(AppContext); const handleLogin = () => { // 替换为实际登录逻辑,获取真实token const mockToken = "valid-user-token"; login(mockToken); }; return ( <div> <h2>登录页面</h2> <button onClick={handleLogin}>模拟登录</button> </div> ); } export default Login;
内容的提问来源于stack exchange,提问作者navix1337
相关产品推荐
相关产品推荐

