You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 09:52:05