useReducer初始状态异常:依赖localStorage时路由跳转错误
问题原因分析
你遇到的核心问题是组件渲染时序导致的路由误跳转:
useReducer初始状态设置为{ user: null },而从localStorage读取并更新用户状态的逻辑放在了useEffect中。useEffect是组件首次渲染完成后才执行的,因此App组件在首次渲染时拿到的user始终是null,触发路由跳转到/login;即使后续useEffect成功更新了用户状态,也无法挽回已经完成的跳转。
解决方案
方案一:同步初始化状态(适合token解码为同步操作的场景)
直接在 useReducer 初始化时读取 localStorage 并解析token,让组件一开始就拥有正确的用户状态,避免首次渲染时的 null 值。
修改 AuthContext.jsx:
import { createContext, useReducer } from "react" import jwt from "jwt-decode" export const AuthContext = createContext(); export const authReducer = (state, action) => { switch(action.type) { case "LOGIN": return { user: action.payload } case "LOGOUT": return { user: null } default: return state } } // 同步生成初始状态 const getInitialState = () => { const refreshToken = localStorage.getItem('refresh_token'); if (refreshToken) { try { const decodedJwt = jwt(refreshToken); // 可选:添加token过期判断 // if (decodedJwt.exp * 1000 > Date.now()) { // return { user: { nama: decodedJwt.nama, role: decodedJwt.role } } // } else { // localStorage.removeItem('refresh_token'); // return { user: null } // } return { user: { nama: decodedJwt.nama, role: decodedJwt.role } } } catch (err) { // 解码失败,清除无效token localStorage.removeItem('refresh_token'); return { user: null } } } return { user: null } } export const AuthContextProvider = ({ children }) => { const [state, dispatch] = useReducer(authReducer, getInitialState()); console.log('AuthContext state: ', state); return ( <AuthContext.Provider value={{...state, dispatch}}> {children} </AuthContext.Provider> ) }
方案二:添加加载状态(适合需要异步验证token的场景)
如果你的 refresh_token 需要调用后端接口验证有效性(而非单纯解码),同步初始化无法满足需求,此时需要添加加载状态,在验证完成前不进行路由跳转。
步骤1:修改 AuthContext.jsx
import { createContext, useReducer, useEffect } from "react" import jwt from "jwt-decode" export const AuthContext = createContext(); export const authReducer = (state, action) => { switch(action.type) { case "LOGIN": return { ...state, user: action.payload } case "LOGOUT": return { ...state, user: null } case "SET_LOADING": return { ...state, loading: action.payload } default: return state } } export const AuthContextProvider = ({ children }) => { const [state, dispatch] = useReducer(authReducer, { user: null, loading: true // 初始加载状态设为true }); useEffect(() => { const initAuth = async () => { dispatch({ type: "SET_LOADING", payload: true }); const refreshToken = localStorage.getItem('refresh_token'); if (refreshToken) { try { const decodedJwt = jwt(refreshToken); // 若需异步验证,替换为接口调用逻辑 // const res = await fetch('/api/validate-token', { // method: 'POST', // body: JSON.stringify({ refreshToken }) // }); // const data = await res.json(); // if (data.valid) { // dispatch({ type: "LOGIN", payload: { nama: data.nama, role: data.role } }); // } else { // localStorage.removeItem('refresh_token'); // } dispatch({ type: "LOGIN", payload: { nama: decodedJwt.nama, role: decodedJwt.role } }); } catch (err) { localStorage.removeItem('refresh_token'); } } dispatch({ type: "SET_LOADING", payload: false }); }; initAuth(); }, []); console.log('AuthContext state: ', state); return ( <AuthContext.Provider value={{...state, dispatch}}> {children} </AuthContext.Provider> ) }
步骤2:修改 App.jsx
import React from 'react' import { Navigate, BrowserRouter, Routes, Route } from 'react-router-dom' import Index from './pages/Index.jsx' import Login from "./pages/auth/Login.jsx" import { useAuthContext } from './hooks/useAuthContext.js' const App = () => { const { user, loading } = useAuthContext(); // 加载中时显示占位内容,不执行路由判断 if (loading) { return <div>加载中...</div>; // 可替换为自定义加载组件 } return ( <BrowserRouter> <Routes> <Route path='/' element={user ? <Index /> : <Navigate to='/login' />} /> <Route path='/login' element={<Login/>} /> </Routes> </BrowserRouter> ) } export default App
内容的提问来源于stack exchange,提问作者Tko Code
相关产品推荐
相关产品推荐

