React+TypeScript中如何结合Context与路由实现用户数据持久化及路由保护
解决方案
1. 修复Context数据持久化问题
当前Context仅存于内存,页面刷新就会丢失。我们可以将用户信息同步存储到localStorage(或你正在使用的Cookies),并在AuthProvider初始化时从存储中读取恢复状态。
修改你的AuthContext代码:
import { createContext, useState, useEffect, ReactNode, useContext } from 'react'; type UserData = { username: string; role: string; }; type AuthContextType = { user: UserData | null; login: (username: string, role: string) => void; logout: () => void; }; const AuthContext = createContext<AuthContextType | undefined>(undefined); type AuthProviderProps = { children: ReactNode; }; export function AuthProvider({ children }: AuthProviderProps) { // 初始化时从localStorage读取用户数据 const [user, setUser] = useState<UserData | null>(() => { const savedUser = localStorage.getItem('authUser'); return savedUser ? JSON.parse(savedUser) : null; }); const login = (username: string, role: string) => { const userData = { username, role }; setUser(userData); // 同步存储到localStorage localStorage.setItem('authUser', JSON.stringify(userData)); }; const logout = () => { setUser(null); localStorage.removeItem('authUser'); // 同时清除token document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; }; // 额外校验:如果token不存在但localStorage有用户数据,说明token过期,自动登出 useEffect(() => { const token = document.cookie.split('; ').find(row => row.startsWith('token='))?.split('=')[1]; if (!token && user) { logout(); } }, [user]); return ( <AuthContext.Provider value={{ user, login, logout }}> {children} </AuthContext.Provider> ); } // 自定义hook简化Context调用 export function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within an AuthProvider'); } return context; }
2. 实现路由权限控制
我们需要创建两个路由守卫组件:
RequireAuth:校验用户是否已登录,未登录则跳转到登录页RequireRole:校验用户角色是否匹配,不匹配则跳转到自身角色的仪表盘
2.1 路由守卫组件实现
import { Navigate, Outlet } from 'react-router-dom'; import { useAuth } from './AuthContext'; // 未登录拦截 export function RequireAuth() { const { user } = useAuth(); if (!user) { return <Navigate to="/login" replace />; } return <Outlet />; } // 角色权限拦截 export function RequireRole({ allowedRoles }: { allowedRoles: string[] }) { const { user } = useAuth(); if (!user) { return <Navigate to="/login" replace />; } // 角色不匹配时跳转到自身角色的仪表盘 if (!allowedRoles.includes(user.role)) { return <Navigate to={`/${user.role}/${user.role}welcome`} replace />; } return <Outlet />; }
2.2 修改App路由配置
用路由守卫包裹对应路由,重构后的App组件:
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; import { RequireAuth, RequireRole } from './AuthGuards'; import { AuthProvider, useAuth } from './AuthContext'; function App() { return ( <AuthProvider> <BrowserRouter> <Navigation /> <Routes> {/* 404页面 */} <Route path="*" element={<h1>Not found page</h1>} /> {/* 未登录可访问路由 */} <Route path="/" element={<Landing />} /> <Route path="/home" element={<Home />} /> <Route path="/login" element={<Login />} /> {/* 已登录用户统一拦截:未登录无法进入 */} <Route element={<RequireAuth />}> {/* Officer专属路由 */} <Route element={<RequireRole allowedRoles={['officer']} />}> <Route path="/officer/*" element={<OfficerPage />}> <Route path="officerwelcome" element={<Welcome user="Encargado" />} /> </Route> </Route> {/* Manager专属路由 */} <Route element={<RequireRole allowedRoles={['manager']} />}> <Route path="/manager/*" element={<ManagerPage />}> <Route path="managerwelcome" element={<Welcome user="Subgerente" />} /> </Route> </Route> {/* Assistant专属路由 */} <Route element={<RequireRole allowedRoles={['assistant']} />}> <Route path="/assistant/*" element={<AssistantPage />}> <Route path="assistantwelcome" element={<Welcome user="Asistente" />} /> </Route> </Route> {/* 已登录用户默认跳转:根据角色跳对应仪表盘 */} <Route path="/dashboard" element={ <AuthRouteRedirect /> } /> </Route> </Routes> </BrowserRouter> </AuthProvider> ); } // 单独抽离默认跳转组件,避免在App组件中直接调用useAuth报错 function AuthRouteRedirect() { const { user } = useAuth(); return <Navigate to={`/${user?.role}/${user?.role}welcome`} replace />; }
2.3 优化Login页面逻辑
确保登录成功后先更新Context状态再跳转(原代码中navigate在login之前,可能导致状态未同步完成):
authService .login(data) .then((response) => { Cookies.set("token", response.token, { expires: 1 }); const { username, role } = response; // 先更新Context状态 login(username, role); // 再跳转 navigate(`/${role}/${role}welcome`, { replace: true }); }) .catch((error) => { console.log(error.response.data); });
3. 是否继续使用Context?
完全可以继续使用Context,你的场景中:
- 状态结构简单(仅用户信息+登录/登出方法)
- 状态不需要跨多层组件频繁更新
- 配合本地存储就能解决持久化问题
除非后续项目复杂度大幅提升(比如需要处理大量异步状态、多模块共享复杂状态),否则无需引入Redux等重型状态管理库,Context完全够用。
内容的提问来源于stack exchange,提问作者Nestor Gonzalez
相关产品推荐
相关产品推荐

