React Router v6+Vite+TS 受保护路由刷新跳转问题求助
React Router DOM v6 受保护路由问题排查与修复
我在React Router DOM v6中开发受保护路由时遇到两个核心问题:
- 页面刷新曾出现空白,修复后刷新特定页面会直接跳转到首页
- 尝试过
createBrowserRouter等多种方案,问题仍未解决
路由配置文件
import React from "react"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import HomePage from "../pages/HomePage"; import Login from "../sign/Admin/Login"; import Signup from "../sign/Admin/Signup"; import { RootState } from "../../store"; import { useSelector } from "react-redux"; import CategoryPage from "../pages/CategoryPage"; import StorePage from "../pages/StorePage"; import ProductPage from "../pages/ProductPage"; import ServicePage from "../pages/ServicePage"; import UserPage from "../pages/UserPage"; import AdminPage from "../pages/AdminPage"; import OrderPage from "../pages/OrderPage"; import PrivateRoutes from "./PrivateRoutes"; const CustomeRoutes: React.FC = () => { // const { isLoading, error, errorMessage } = useSelector((state: RootState) => state.admin); return ( <> <Router> <Routes> <Route element={<PrivateRoutes />}> <Route element={<HomePage />} path="/" /> <Route path="/categories" element={<CategoryPage />} /> <Route path="/stores" element={<StorePage />} /> <Route path="/products" element={<ProductPage />} /> <Route path="/services" element={<ServicePage />} /> <Route path="/users" element={<UserPage />} /> <Route path="/admins" element={<AdminPage />} /> <Route path="/orders" element={<OrderPage />} /> </Route> <Route path="/login" element={<Login />} /> <Route path="/signup" element={<Signup />} /> </Routes> </Router> </> ); }; export default CustomeRoutes;
App.tsx
import useLocalizeDocumentAttributes from "./i18n/useLocalizeDocumentAttributes"; import { AppDispatch, RootState } from "./store"; import { useDispatch, useSelector } from "react-redux"; import { useEffect, useState } from "react"; import { getAdminByToken } from "./store/slices/admin"; import CutomeRoutes from "./components/routes"; function App() { useLocalizeDocumentAttributes(); const dispatch = useDispatch<AppDispatch>(); // const { isLoading, error, errorMessage } = useSelector((state: RootState) => state.admin); useEffect(() => { const tokenFromStorage = localStorage.getItem("user-token"); if (tokenFromStorage) { dispatch(getAdminByToken(tokenFromStorage)); } }, [dispatch]); return ( <> <CutomeRoutes /> </> ); } export default App;
PrivateRoutes组件
const PrivateRoutes = () => { const { token, isLoading } = useSelector((state: RootState) => state.admin); console.log("PrivateRoutes=>", { token }); React.useEffect(() => {}, [token]); // if (isLoading) return <div>Loading...</div>; return !!token ? <Outlet /> : <Navigate to="/login" />; };
登录处理逻辑
const onSubmit: SubmitHandler<FormFields> = async (inputData, event) => { try { console.log(inputData); if (loginKey) { // If loginKey exists, dispatch acceptLoginKey action dispatch(acceptAdminLoginKey({ phone: inputData.phoneNumber, loginKey: parseInt(inputData.loginKey) })); } else { dispatch(loginAdmin(inputData.phoneNumber)); } } catch (error) { console.log(error); setError("root", { message: "There's something wrong " + error, }); } }; useEffect(() => { if (token) { const { from } = location.state || { from: { pathname: "/" } }; navigate(from, { replace: true }); } }, [token]);
问题根源与修复方案
1. 核心问题分析
- 刷新跳转首页:
PrivateRoutes未处理isLoading状态,页面刷新时getAdminByToken请求还在进行,此时token为undefined,直接触发跳转到登录页;登录逻辑中默认跳转路径为/,导致后续拿到token后跳转到首页而非当前刷新的页面。 - 曾出现的空白问题:加载状态缺失导致验证未完成时无内容渲染,或路由Outlet使用不规范。
2. 具体修复步骤
完善PrivateRoutes的状态处理
修改PrivateRoutes,在验证过程中展示加载界面,并记录当前跳转路径:
import { Outlet, Navigate } from "react-router-dom"; import { useSelector } from "react-redux"; import { RootState } from "../../store"; const PrivateRoutes = () => { const { token, isLoading } = useSelector((state: RootState) => state.admin); // 加载中时显示加载组件,避免错误跳转 if (isLoading) { return <div>加载中...</div>; // 可替换为项目全局加载组件 } // 已登录则渲染子路由,否则跳转到登录页并记录当前路径 return !!token ? ( <Outlet /> ) : ( <Navigate to="/login" state={{ from: window.location.pathname }} replace /> ); }; export default PrivateRoutes;
修正登录后的跳转逻辑
确保登录成功后跳转到之前记录的路径:
// 登录组件中的useEffect useEffect(() => { if (token) { // 优先使用跳转前的路径,默认才跳转到首页 const { from } = location.state || { from: { pathname: "/" } }; navigate(from, { replace: true }); } }, [token, navigate, location.state]);
确保Redux异步状态正确更新
检查getAdminByToken的Redux Slice逻辑,确保isLoading状态在请求开始/结束时正确切换:
// 示例Redux Slice中的异步处理逻辑 import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; export const getAdminByToken = createAsyncThunk( "admin/getAdminByToken", async (token: string, { rejectWithValue }) => { try { const response = await api.get("/admin/profile", { headers: { Authorization: `Bearer ${token}` } }); return response.data; } catch (error) { return rejectWithValue(error.response?.data?.message || "验证失败"); } } ); const adminSlice = createSlice({ name: "admin", initialState: { token: localStorage.getItem("user-token"), isLoading: false, error: false, errorMessage: "", adminData: null }, reducers: {}, extraReducers: (builder) => { builder .addCase(getAdminByToken.pending, (state) => { state.isLoading = true; state.error = false; }) .addCase(getAdminByToken.fulfilled, (state, action) => { state.isLoading = false; state.adminData = action.payload; }) .addCase(getAdminByToken.rejected, (state, action) => { state.isLoading = false; state.error = true; state.errorMessage = action.payload as string; localStorage.removeItem("user-token"); state.token = null; }); } }); export default adminSlice.reducer;
内容的提问来源于stack exchange,提问作者Ehab reda
相关产品推荐
相关产品推荐

