使用useContext实现导航栏登录/登出按钮无刷新切换时状态自动变动问题
问题成因及修复方案
根本原因
你的代码出现状态反复变动是因为触发了无限重渲染循环,具体问题点如下:
- useEffect依赖项配置错误:你将
loggedin加入了useEffect依赖列表,同时在useEffect内部调用setLoggedin修改该状态。每次状态修改都会触发组件重渲染,重渲染后检测到loggedin变化又会再次执行useEffect,形成死循环,所以按钮会在登录/登出状态之间不停切换。 - 状态更新逻辑错误:useEffect内直接对
loggedin取反的逻辑和实际登录状态(token是否存在)完全脱钩,没有按照你需要的「token存在则为登录态」的规则更新状态。 - token读取非响应式:你在组件顶层直接读取
localStorage.getItem('access_token'),该值只会在组件首次渲染时获取一次,后续localStorage内token变化时这个变量不会自动更新,无法触发状态同步。 - 登出逻辑缺失状态更新:当前登出按钮的点击事件只删除了localStorage里的token,没有主动更新Context内的
loggedin状态,状态不会自动同步。
修复方案
1. 基础修复(直接修改现有代码)
修正Nav.js的逻辑即可解决循环问题:
const removeToken = () => { localStorage.removeItem('access_token'); }; const { loggedin, setLoggedin } = useLoginContext(); // 仅在组件首次挂载时同步localStorage的登录状态 useEffect(() => { const token = localStorage.getItem('access_token'); // 直接根据token是否存在设置状态,不要依赖当前的loggedin值取反 setLoggedin(!!token); }, [setLoggedin]); return ( ... <NavRight> {loggedin ? ( <NavSigninBtn onClick={() => { removeToken(); // 删除token后主动更新登录状态 setLoggedin(false); }} > 登出 </NavSigninBtn> ) : ( <NavSigninBtn onClick={() => { history.push('/signin'); }} > 登录 </NavSigninBtn> )} </NavRight> )
另外注意在登录页登录成功、存入token后,需要主动调用setLoggedin(true),就能实现无需刷新切换导航栏状态。
2. 优化方案(封装Context逻辑避免分散处理)
建议将登录状态相关的逻辑全部封装到Context内,统一处理token读写和状态更新,减少出错概率:
// Context API 优化后代码 export function LoginContextProvider({ children }) { // 初始化时直接读取localStorage设置初始状态 const [loggedin, setLoggedin] = useState(!!localStorage.getItem('access_token')); // 封装登录方法,调用时直接传入token即可 const login = (token) => { localStorage.setItem('access_token', token); setLoggedin(true); }; // 封装登出方法,统一处理token删除和状态更新 const logout = () => { localStorage.removeItem('access_token'); setLoggedin(false); }; return ( <LoginContext.Provider value={{ loggedin, login, logout }}> {children} </LoginContext.Provider> ); }
封装后Nav.js的代码可以简化为:
const { loggedin, logout } = useLoginContext(); return ( ... <NavRight> {loggedin ? ( <NavSigninBtn onClick={logout}> 登出 </NavSigninBtn> ) : ( <NavSigninBtn onClick={() => { history.push('/signin'); }} > 登录 </NavSigninBtn> )} </NavRight> )
内容的提问来源于stack exchange,提问作者Taesoo
相关产品推荐
相关产品推荐

