React Router 6.3.0中如何将Login组件的state传递给RequireAuth组件
解决React Router 6中受保护路由共享登录状态的问题
你的核心问题是Login组件内部的user和isLoading状态无法直接传递给RequireAuth组件——二者属于不同路由下的组件,没有直接的父子props传递关系。下面给出两种可行的解决方案:
方案一:使用React Context全局管理认证状态
通过创建全局Context来共享用户信息和加载状态,让所有组件都能访问到认证相关数据。
1. 创建AuthContext
新建AuthContext.js文件:
import { createContext, useContext, useState, useEffect } from 'react'; import axios from 'axios'; const AuthContext = createContext(); export function AuthProvider({ children }) { const [user, setUser] = useState(null); const [isAuthLoading, setIsAuthLoading] = useState(true); // 页面刷新时自动验证token,恢复用户状态 useEffect(() => { const initAuth = async () => { const token = localStorage.getItem('access_token'); if (token) { try { const res = await axios.get('http://www.example.com/auth/me', { headers: { Authorization: `Bearer ${token}` } }); setUser(res.data); } catch (err) { // token失效,清除存储 localStorage.removeItem('access_token'); localStorage.removeItem('refresh_token'); } } setIsAuthLoading(false); }; initAuth(); }, []); return ( <AuthContext.Provider value={{ user, setUser, isAuthLoading, setIsAuthLoading }}> {children} </AuthContext.Provider> ); } // 自定义钩子,简化Context访问 export function useAuth() { return useContext(AuthContext); }
2. 用AuthProvider包裹App组件
修改App.js,确保所有组件都能访问Context:
import { AuthProvider } from './path/to/AuthContext'; import { Routes, Route } from 'react-router-dom'; import RequireAuth from './path/to/RequireAuth'; import Dashboard from './path/to/Dashboard'; import Products from './path/to/Products'; import CreateProduct from './path/to/CreateProduct'; import Login from './path/to/Login'; import { ToastContainer } from 'react-toastify'; function App() { return ( <AuthProvider> <div className="App"> <Routes> {/* 外层路由统一加保护,子路由无需重复添加 */} <Route path="/" element={<RequireAuth><Dashboard /></RequireAuth>}> <Route path="products" element={<Products />} /> <Route path="products/create" element={<CreateProduct />} /> </Route> <Route path="/login" element={<Login />} /> </Routes> <ToastContainer /> </div> </AuthProvider> ); } export default App;
3. 修改Login组件,更新全局状态
import { useMutation } from "@tanstack/react-query"; import axios from "axios"; import React, { useState } from "react"; import { useForm } from "react-hook-form"; import { useLocation, useNavigate } from "react-router-dom"; import useToken from "../../hooks/useToken"; import Loading from "../shared/Loading"; import { useAuth } from '../../path/to/AuthContext'; const Login = () => { const { setUser, setIsAuthLoading } = useAuth(); const [error, setError] = useState(""); const navigate = useNavigate(); const location = useLocation(); const from = location.state?.from?.pathname || "/"; const { register, handleSubmit, formState: { errors } } = useForm(); let errorElement; const userLogin = async (data) => { const response = await axios.post( "http://www.example.com/auth/login", data ); return response.data; }; const { mutate, isLoading, isError } = useMutation(userLogin, { onSuccess: (data) => { setUser(data); // 登录成功后跳转到之前访问的页面 navigate(from, { replace: true }); }, onError: (error) => { setError(error); }, onMutate: () => { setIsAuthLoading(true); }, onSettled: () => { setIsAuthLoading(false); } }); if (isLoading) { return <Loading />; } if (isError) { errorElement = ( <p className="px-1 pb-2"> <small className="text-red-500">Invalid user credentials</small> </p> ); } const onSubmit = (data) => { mutate(data); }; return ( {/* 你的表单JSX代码 */} <form onSubmit={handleSubmit(onSubmit)}> {/* 表单字段 */} {errorElement} <button type="submit">登录</button> </form> ); }; export default Login;
4. 修改RequireAuth组件,从Context获取状态
import React from "react"; import { Navigate, useLocation } from "react-router-dom"; import Loading from "../Shared/Loading"; import { useAuth } from '../path/to/AuthContext'; function RequireAuth({ children }) { const location = useLocation(); const { user, isAuthLoading } = useAuth(); // 认证校验中显示加载 if (isAuthLoading) { return <Loading />; } // 未登录则跳转到登录页 if (!user) { return <Navigate to="/login" state={{ from: location }} replace />; } return children; } export default RequireAuth;
方案二:利用TanStack Query缓存共享用户状态
既然你已经在使用TanStack Query,可以直接用它的缓存来存储用户信息,无需额外创建Context。
1. 修改Login组件,将用户信息存入Query缓存
import { useMutation, useQueryClient } from "@tanstack/react-query"; // ...其他导入 const Login = () => { const queryClient = useQueryClient(); // ...其他代码 const { mutate, isLoading, isError } = useMutation(userLogin, { onSuccess: (data) => { // 将用户信息存入Query缓存,key为['user'] queryClient.setQueryData(['user'], data); navigate(from, { replace: true }); }, // ...其他回调 }); // ...剩余代码 };
2. 修改RequireAuth组件,从Query缓存获取用户信息
import React from "react"; import { Navigate, useLocation } from "react-router-dom"; import Loading from "../Shared/Loading"; import { useQuery } from '@tanstack/react-query'; import axios from 'axios'; function RequireAuth({ children }) { const location = useLocation(); // 从缓存获取用户信息,缓存不存在时自动请求验证 const { data: user, isLoading } = useQuery(['user'], async () => { const token = localStorage.getItem('access_token'); if (!token) return null; // 请求用户信息接口验证token有效性 const res = await axios.get('http://www.example.com/auth/me', { headers: { Authorization: `Bearer ${token}` } }); return res.data; }, { staleTime: Infinity, // 避免重复请求 retry: false }); if (isLoading) { return <Loading />; } if (!user) { return <Navigate to="/login" state={{ from: location }} replace />; } return children; } export default RequireAuth;
关键说明
- 避免重复嵌套RequireAuth:外层路由添加RequireAuth后,所有子路由会自动继承保护,无需每个子路由都包裹。
- 页面刷新状态保留:两种方案都处理了刷新页面后的用户状态恢复问题,通过读取localStorage中的token重新验证。
- 加载状态区分:Login中的
isLoading是登录请求的加载状态,而RequireAuth中的加载是认证校验的状态,二者逻辑不同,全局管理更合理。
内容的提问来源于stack exchange,提问作者Thakur Saad
相关产品推荐
相关产品推荐

