使用createBrowserRouter时自定义Hook中useNavigate报错的解决咨询
解决方案
方案1:移除useUser中的useNavigate,改为外部传入跳转逻辑
把useUser里的登录/登出跳转逻辑抽离,让调用Hook的组件自行传入跳转函数,既解除对Router上下文的依赖,也让Hook更通用:
// 改造后的useUser function useUser(onLoginSuccess, onLogoutSuccess) { const [user, setUser] = useState(null); const [permissions, setPermissions] = useState([]); const login = async (credentials) => { // 登录请求逻辑 const res = await api.login(credentials); setUser(res.user); setPermissions(res.permissions); // 调用外部传入的跳转函数 onLoginSuccess?.(); }; const logout = async () => { await api.logout(); setUser(null); setPermissions([]); onLogoutSuccess?.(); }; return { user, permissions, login, logout }; }
在Router内部的组件中使用时,传入useNavigate的跳转方法:
// 登录组件示例 function LoginPage() { const navigate = useNavigate(); const { login } = useUser( () => navigate('/dashboard'), () => navigate('/login') ); // 登录表单逻辑... }
如果需要在App组件中获取用户权限配置路由,可以单独写一个不依赖跳转的初始化函数:
// App.jsx async function getInitialUser() { try { const res = await api.getCurrentUser(); return { user: res.user, permissions: res.permissions }; } catch { return { user: null, permissions: [] }; } } function App() { const [userInfo, setUserInfo] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { getInitialUser().then(data => { setUserInfo(data); setLoading(false); }); }, []); if (loading) return <div>Loading...</div>; const router = createBrowserRouter([ // 根据userInfo.permissions配置路由 { path: '/', element: <Layout />, children: [ { path: 'dashboard', element: <Dashboard /> }, // 其他路由... ] } ]); return <RouterProvider router={router} />; }
方案2:将权限校验移到路由的loader/action或受保护路由组件中
不在App初始化路由时依赖用户权限,而是把校验逻辑放到路由的loader函数里,或者封装受保护路由组件:
方式A:用路由loader做权限校验
// 封装权限校验loader async function requireAuth({ request }) { const user = await api.getCurrentUser(); if (!user) { return redirect('/login?from=' + new URL(request.url).pathname); } // 校验权限 if (!user.permissions.includes('dashboard')) { return redirect('/forbidden'); } return user; } // 路由配置 const router = createBrowserRouter([ { path: '/dashboard', element: <Dashboard />, loader: requireAuth }, { path: '/login', element: <LoginPage /> } ]); // App.jsx直接渲染RouterProvider function App() { return <RouterProvider router={router} />; }
方式B:封装受保护路由组件
// ProtectedRoute.jsx function ProtectedRoute({ children, requiredPermission }) { const { user, permissions } = useUser(); const navigate = useNavigate(); useEffect(() => { if (!user) { navigate('/login'); } else if (requiredPermission && !permissions.includes(requiredPermission)) { navigate('/forbidden'); } }, [user, permissions, requiredPermission, navigate]); if (!user || (requiredPermission && !permissions.includes(requiredPermission))) { return <div>Loading...</div>; } return children; } // 路由配置 const router = createBrowserRouter([ { path: '/', element: <Layout />, children: [ { path: 'dashboard', element: ( <ProtectedRoute requiredPermission="dashboard"> <Dashboard /> </ProtectedRoute> ) }, // 其他路由... ] } ]); // App.jsx function App() { return <RouterProvider router={router} />; }
方案3:将useUser中的useNavigate替换为Router的navigator实例
createBrowserRouter返回的router对象包含navigator属性,可通过useRouterContext获取,这样useUser仍能保留跳转逻辑,但必须在RouterProvider渲染的组件树中使用:
// useUser.js import { useRouterContext } from 'react-router-dom'; function useUser() { const { navigator } = useRouterContext(); const [user, setUser] = useState(null); const [permissions, setPermissions] = useState([]); const login = async (credentials) => { const res = await api.login(credentials); setUser(res.user); setPermissions(res.permissions); navigator('/dashboard'); }; const logout = async () => { await api.logout(); setUser(null); setPermissions([]); navigator('/login'); }; return { user, permissions, login, logout }; }
注意:此方案下useUser不能在App组件初始化路由时调用,仅能在路由内部的组件中使用。
内容的提问来源于stack exchange,提问作者Aw3same
相关产品推荐
相关产品推荐

