React Router Dom v6刷新后navigate(-1)需点击两次的问题求助
React Router v6.3 受保护路由刷新后历史栈异常问题解决方案
核心问题分析
刷新受保护页面时,RequireAuth组件在用户状态未初始化完成前就执行了重定向到Login页,随后Login组件检测到已登录又跳转回目标页,即便使用replace: true,仍会在历史栈中残留冗余记录,导致navigate(-1)需要多次触发才能返回上一页。
方案1:在RequireAuth中增加加载状态,避免过早重定向
在权限校验组件中先判断用户状态是否处于“未初始化”状态,此时不执行重定向,而是显示加载界面,等状态确定后再处理权限逻辑。
代码实现
import { Navigate, useLocation } from 'react-router-dom'; function RequireAuth({ user, loading, children }) { const location = useLocation(); // 状态未加载完成时,渲染加载组件 if (loading) { return <div>加载中...</div>; // 替换为项目自定义Loading组件 } // 已登录,渲染受保护页面内容 if (user) { return children; } // 未登录,重定向到登录页并携带当前路径 return <Navigate to="/login" state={{ from: location }} replace />; }
路由配置使用
// 假设从Context/Redux获取用户状态和加载状态 const { currentUser, userLoading } = useAuth(); <Route path="/cms/*" element={ <RequireAuth user={currentUser} loading={userLoading}> <CmsLayout /> </RequireAuth> }> <Route path="manage" element={<Manage />} /> <Route path="detail/:id" element={<Detail />} /> </Route>
方案2:优化Login组件的跳转逻辑
确保登录状态的同步获取(比如从localStorage读取),并仅在必要时使用replace替换历史记录,避免冗余跳转。
代码实现
import { useLocation, useNavigate, useEffect } from 'react-router-dom'; function Login() { const navigate = useNavigate(); const location = useLocation(); const from = location.state?.from?.pathname || '/'; // 同步获取本地存储的用户状态 const getUser = () => JSON.parse(localStorage.getItem('user')); // 登录成功处理 const handleLogin = async (formData) => { const user = await api.login(formData); localStorage.setItem('user', JSON.stringify(user)); // 用replace替换当前Login页记录,避免历史栈残留 navigate(from, { replace: true }); }; // 组件挂载时检查已登录状态,直接跳转回目标页 useEffect(() => { const user = getUser(); if (user) { navigate(from, { replace: true }); } }, [navigate, from]); return ( // 登录表单UI <div className="login-form"> {/* 表单内容 */} </div> ); }
方案3:升级到React Router v6.4+,使用路由Loader做权限校验(推荐)
v6.4+新增的路由loader函数可以在路由加载前完成权限校验,从根源避免组件渲染阶段的重定向问题,是官方推荐的权限校验方式。
代码实现
import { createBrowserRouter, RouterProvider, Navigate } from 'react-router-dom'; // 全局权限校验Loader async function authLoader() { // 同步获取用户状态 const user = JSON.parse(localStorage.getItem('user')); if (!user) { // 未登录,抛出重定向响应 throw new Response(null, { status: 302, headers: { Location: `/login?from=${window.location.pathname}` }, }); } return user; } // 登录页Loader,处理已登录跳转 async function loginLoader() { const user = JSON.parse(localStorage.getItem('user')); if (user) { const from = new URLSearchParams(window.location.search).get('from') || '/'; throw new Response(null, { status: 302, headers: { Location: from }, }); } return null; } // 路由配置 const router = createBrowserRouter([ { path: '/login', element: <Login />, loader: loginLoader, }, { path: '/cms', element: <CmsLayout />, loader: authLoader, // 整个CMS路由组继承权限校验 children: [ { path: 'manage', element: <Manage /> }, { path: 'detail/:id', element: <Detail /> }, ], }, // 公开路由 { path: '/', element: <Home /> }, ]); function App() { return <RouterProvider router={router} />; }
内容的提问来源于stack exchange,提问作者Hiếu Nguuyễn Minh
相关产品推荐
相关产品推荐

