React.js中LocalStorage管理认证中间件:token无法立即存储问题排查
问题描述
调用认证API后,将返回的token存入LocalStorage,随后跳转至需要token验证的仪表盘页面,但跳转后仍被拦截回登录页,token无法立即生效。
原因分析
- 路由规则一次性固化:App组件中的
useEffect依赖为空数组,仅在组件首次挂载时执行一次,生成所有路由规则。此时LocalStorage中还没有token,所以需要认证的路由(如仪表盘)对应的element被固定设置为跳转登录页的<Navigate>组件。后续登录存入token后,路由规则不会重新生成,跳转时依然使用旧规则,导致被拦截。 - 验证逻辑非动态:路由的认证判断是在初始化时完成的,并非每次路由渲染时动态检查LocalStorage的token状态,因此token更新后无法触发验证逻辑的更新。
解决方案建议
方案1:使用私有路由组件(推荐)
创建一个专门的私有路由组件,每次渲染时动态检查token状态,替代一次性的路由规则判断:
- 新建
PrivateRoute.js组件:
import { Navigate } from 'react-router-dom'; export const PrivateRoute = ({ children }) => { const hasToken = localStorage.getItem('token'); return hasToken ? children : <Navigate to='/auth/login' state={{ from: window.location.pathname }} />; };
- 修改App.js中的路由生成逻辑:
// 导入PrivateRoute import { PrivateRoute } from './components/PrivateRoute'; // ...其他代码 useEffect(() => { const getMenu = routes.map((route, index) => { return (route.component) ? ( <Route key={index} path={route.path} exact={route.exact} name={route.name} element={route.auth ? <PrivateRoute>{route.component}</PrivateRoute> : route.component} /> ) : null; }); setMenu(getMenu); setMount(true); }, []);
这样每次访问需要认证的路由时,都会实时检查LocalStorage中的token状态,登录后跳转即可正常访问。
方案2:监听token变化更新路由
在App组件中监听LocalStorage的变化,当token更新时重新生成路由规则:
useEffect(() => { const updateRouteMenu = () => { const newMenu = routes.map((route, index) => { return (route.component) ? ( <Route key={index} path={route.path} exact={route.exact} name={route.name} element={route.auth ? localStorage.getItem('token') ? route.component : <Navigate to='/auth/login' state={{ from: route.path }} /> : route.component} /> ) : null; }); setMenu(newMenu); }; // 初始化生成路由 updateRouteMenu(); setMount(true); // 监听LocalStorage变化(跨标签页) window.addEventListener('storage', updateRouteMenu); // 当前标签页修改token后手动触发更新 const handleTokenUpdate = () => updateRouteMenu(); window.addEventListener('tokenUpdate', handleTokenUpdate); return () => { window.removeEventListener('storage', updateRouteMenu); window.removeEventListener('tokenUpdate', handleTokenUpdate); }; }, []);
然后在登录提交函数中,存入token后手动触发更新:
const handleSubmit = async(event) => { event.preventDefault(); const data = new FormData(event.currentTarget); const loginData = { email: data.get('email'), password: data.get('password'), }; const user = await login(loginData); if (user.token) { localStorage.setItem('token', user.token); // 手动触发路由更新 window.dispatchEvent(new Event('tokenUpdate')); navigate('/'); } else { toast(user); } };
方案3:用状态管理存储token
将token存入React Context(或其他状态管理工具),利用状态变化触发组件重新渲染,自动更新路由规则:
- 完善AuthContext:
// context/AuthContext.js import { createContext, useState, useEffect } from 'react'; export const AuthContext = createContext(); export const AuthProvider = ({ children }) => { // 从LocalStorage初始化token状态 const [token, setToken] = useState(localStorage.getItem('token')); // 状态同步到LocalStorage useEffect(() => { if (token) { localStorage.setItem('token', token); } else { localStorage.removeItem('token'); } }, [token]); // 登录方法 const login = (newToken) => { setToken(newToken); }; return ( <AuthContext.Provider value={{ token, login }}> {children} </AuthContext.Provider> ); };
- 在登录组件中使用Context:
import { useContext } from 'react'; import { AuthContext } from '../context/AuthContext'; // ...其他代码 const { login } = useContext(AuthContext); const handleSubmit = async(event) => { event.preventDefault(); const data = new FormData(event.currentTarget); const loginData = { email: data.get('email'), password: data.get('password'), }; const user = await login(loginData); if (user.token) { login(user.token); navigate('/'); } else { toast(user); } };
- 修改App.js中的路由逻辑:
const { token } = useContext(AuthContext); useEffect(() => { const getMenu = routes.map((route, index) => { return (route.component) ? ( <Route key={index} path={route.path} exact={route.exact} name={route.name} element={route.auth ? token ? route.component : <Navigate to='/auth/login' state={{ from: route.path }} /> : route.component} /> ) : null; }); setMenu(getMenu); setMount(true); }, [token]); // 依赖token状态,变化时重新生成路由
这样token状态更新时,App组件会重新渲染并生成新的路由规则,跳转后即可正常访问仪表盘。
内容的提问来源于stack exchange,提问作者Christian Rainer Sassmann
相关产品推荐
相关产品推荐

