React中如何在组件切换时调用API验证用户有效性?
问题描述
我开发了一个用户认证应用,用户登录后将其数据存储在Redux Store中,通过Redux管理认证状态,但无法获取实时数据。例如,用户在会话中途被封禁,除非访问服务器端的受保护路由,否则无法自动登出。
于是我创建了一个自定义Hook来调用API向服务器验证用户。问题在于我创建了ProtectedRoute组件并包裹所有路由,期望切换组件时触发API调用,但ProtectedRoute作为父组件无状态变化,导致每次访问组件时不会触发API调用。
一种方法是在每个用户访问的组件中调用该Hook,但这并非高效方案。请问如何在子组件切换时自动调用API?或者有其他实现该功能的方法?
路由结构
<Route element={<ProtectedRoutes />}> <Route element={<DashboardHeader />}> <Route path="dashboard" element={<Dashboard />}></Route> <Route path="classroom/:classroom_id" element={<ClassroomLayout />}> <Route path="summary" element={<ClassroomSummary />} errorElement={<Error />} /> </Route> </Route> </Route>
ProtectedRoute组件代码
// Protected Route 中调用 useAuth Hook,Hook本身功能正常! const ProtectedRoutes: React.FC<DashboardProps> = () => { const role = useRole(); const navigate = useNavigate() const { loading, user, error } = useAuth(role); if (loading) return null if (error) navigate('/'); return user ? <Outlet /> : <Navigate to='/' /> }
解决方案
1. 监听路由变化触发验证
利用React Router的useLocation钩子监听路由路径变化,每次路由切换时重新调用认证API。需要先给useAuth钩子添加手动触发验证的方法:
改造useAuth钩子
const useAuth = (role: string) => { const [loading, setLoading] = useState(true); const [user, setUser] = useState<User | null>(null); const [error, setError] = useState<string | null>(null); // 封装验证逻辑为可调用函数 const validateUser = async () => { setLoading(true); try { const token = localStorage.getItem('token'); if (!token) throw new Error('无有效令牌'); const res = await fetch('/api/validate-user', { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (!res.ok) throw new Error('用户验证失败'); const data = await res.json(); // 校验用户角色与状态 if (data.role !== role || data.status === 'banned') { throw new Error('用户无权限或已被封禁'); } setUser(data); setError(null); } catch (err) { setError((err as Error).message); setUser(null); } finally { setLoading(false); } }; // 初始化验证 useEffect(() => { validateUser(); }, [role]); return { loading, user, error, revalidate: validateUser }; };
更新ProtectedRoute组件
import { useLocation, useEffect } from 'react-router-dom'; const ProtectedRoutes: React.FC<DashboardProps> = () => { const role = useRole(); const navigate = useNavigate(); const location = useLocation(); const { loading, user, error, revalidate } = useAuth(role); // 路由路径变化时触发重新验证 useEffect(() => { revalidate(); }, [location.pathname, revalidate]); if (loading) return null; if (error) navigate('/'); return user ? <Outlet /> : <Navigate to='/' />; };
2. 全局定时轮询增强实时性
如果需要在用户无操作时也同步状态(比如中途被封禁),可以添加定时轮询机制,每隔固定时间自动验证:
const ProtectedRoutes: React.FC<DashboardProps> = () => { const role = useRole(); const navigate = useNavigate(); const location = useLocation(); const { loading, user, error, revalidate } = useAuth(role); // 路由切换时立即验证 useEffect(() => { revalidate(); }, [location.pathname, revalidate]); // 每30秒轮询一次(可根据需求调整间隔) useEffect(() => { const interval = setInterval(() => { revalidate(); }, 30000); // 组件卸载时清除定时器 return () => clearInterval(interval); }, [revalidate]); if (loading) return null; if (error) navigate('/'); return user ? <Outlet /> : <Navigate to='/' />; };
3. WebSocket服务器推送实现实时同步
如果需要极致的实时性,可通过WebSocket让服务器主动推送用户状态变更(如被封禁),前端收到消息后立即处理:
import { useEffect } from 'react'; import { useDispatch } from 'react-redux'; import { logout } from './redux/authSlice'; const ProtectedRoutes: React.FC<DashboardProps> = () => { const role = useRole(); const navigate = useNavigate(); const dispatch = useDispatch(); const { loading, user, error, revalidate } = useAuth(role); const location = useLocation(); useEffect(() => { revalidate(); }, [location.pathname, revalidate]); // 初始化WebSocket连接 useEffect(() => { const socket = new WebSocket('ws://your-server-domain/ws/user-status'); socket.onopen = () => { // 发送用户标识给服务器 socket.send(JSON.stringify({ userId: user?.id })); }; socket.onmessage = (event) => { const message = JSON.parse(event.data); if (message.type === 'USER_BANNED') { // 清除认证状态并跳转登录页 dispatch(logout()); navigate('/'); socket.close(); } }; socket.onerror = () => { console.error('WebSocket连接失败'); }; return () => { socket.close(); }; }, [user?.id, dispatch, navigate]); if (loading) return null; if (error) navigate('/'); return user ? <Outlet /> : <Navigate to='/' />; };
内容的提问来源于stack exchange,提问作者Ahammed Zulaikh
相关产品推荐
相关产品推荐

