如何结合应用上下文使用createBrowserRouter与RouterProvider
解决React Router v6.8+中全局上下文与RouterProvider的结合问题
核心问题分析
你的代码存在两个关键问题:
useNavigate钩子必须在路由上下文(RouterProvider内部)中使用,外层包裹的ApiProvider调用它会直接报错- 在渲染阶段直接调用
navigate会触发无限重渲染循环
同时你需要实现:
- 全局上下文能控制路由导航(无权限时跳转到403)
- 静态Header不随路由变更重渲染
- 使用
createBrowserRouter以支持数据API相关功能(如卸载前提示)
解决方案步骤
1. 调整组件层级结构
将 ApiProvider 和静态Header放入根路由的Element组件中,确保路由钩子能正常访问,同时Header保持静态:
RouterProvider作为最外层提供路由上下文- 根路由的Element包含静态Header、
ApiProvider和Outlet(用于渲染子路由组件)
2. 修复ApiProvider的导航逻辑
用 useEffect 替代渲染阶段直接调用 navigate,避免无限重渲染;同时用 memo 和 useMemo 优化上下文的重渲染。
完整代码示例
应用上下文(ApiContext)
import { memo, createContext, useState, useEffect, useMemo, useNavigate } from 'react'; export const ApiContext = createContext<{ token?: string; setToken: (token: string | undefined) => void }>({ setToken: () => {} }); export const ApiProvider = memo(({ children }: { children: React.ReactNode }) => { const navigate = useNavigate(); const [token, setToken] = useState<string>(); // 使用useEffect处理导航,避免渲染阶段触发重渲染 useEffect(() => { if (!token && window.location.pathname !== '/403') { navigate('/403'); } }, [token, navigate]); // 用useMemo确保context值稳定,避免不必要的子组件重渲染 const contextValue = useMemo(() => ({ token, setToken }), [token]); return ( <ApiContext.Provider value={contextValue}> {children} </ApiContext.Provider> ); });
根路由组件(Root)
import { Outlet } from 'react-router-dom'; import { ApiProvider } from './ApiContext'; import { Header } from './Header'; // 用memo包裹根组件,避免无关重渲染 export const Root = memo(() => { return ( <div className="app-container"> {/* 静态Header,仅在根组件渲染时渲染一次 */} <Header /> {/* ApiProvider包裹子路由组件,确保全局上下文生效 */} <ApiProvider> <Outlet /> </ApiProvider> </div> ); });
入口文件
import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import { Root } from './Root'; import { Login } from './Login'; import { Home } from './Home'; const router = createBrowserRouter([ { path: '/', element: <Root />, children: [ { path: '', element: <Home /> }, { path: '403', element: <Login /> }, ], // 在这里添加数据API相关配置,比如卸载前提示 loader: async () => { // 示例:加载初始化数据 return null; }, shouldRevalidate: () => false, // 卸载前提示示例 unstable_onBeforeUnload: () => { return '你确定要离开吗?'; } } ]); ReactDOM.createRoot(document.getElementById('root')!).render( <RouterProvider router={router} /> );
关键优化点说明
- 层级调整:将
ApiProvider放入路由上下文内部,确保useNavigate能正常使用;静态Header作为根组件的一部分,不会随子路由变化重渲染 - 导航逻辑修复:用
useEffect监听token变化,仅在需要时触发导航,同时判断当前路径避免重复跳转 - 重渲染优化:用
memo包裹ApiProvider和Root组件,用useMemo稳定上下文值,减少不必要的组件更新
内容的提问来源于stack exchange,提问作者Benoit Meunier
相关产品推荐
相关产品推荐

