React Hook顶层调用错误修复及SpringBoot会话管理方案求助
问题修复与优化方案
一、修复React Hook调用错误
你的核心错误是自定义Hook useSetupInterceptors在非组件/非Hook的顶层文件中调用,违反了React Hook的规则:Hook只能在React函数组件或自定义Hook内部调用。以下是具体修复步骤:
1. 重构EndpointConfig.js
移除顶层Hook调用,只保留配置和动态创建axios实例的逻辑:
import axios from 'axios'; const config = window.APP_CONFIG; const endPointURLIP = config.endPointIP || "localhost"; // 从cookie获取token的工具函数 export const getTokenFromCookie = () => { const cookies = document.cookie.split(';').map(cookie => cookie.trim()); for (const cookie of cookies) { if (cookie.startsWith('_auth=')) { const tokenCookie = cookie.split('='); if (tokenCookie.length === 2) { return tokenCookie[1]; } } } return null; }; // 动态创建axios实例,每次调用都会获取最新token export const createAuthAxios = () => { const API_BASE_URL = `http://${endPointURLIP}:9081`; const authToken = getTokenFromCookie(); const AUTHENTICATION_HEADER_TOKEN = authToken ? `Bearer ${authToken}` : null; return axios.create({ baseURL: API_BASE_URL, headers: { Authorization: AUTHENTICATION_HEADER_TOKEN, }, }); }; const endpointConfig = { BASE_URL: `http://${endPointURLIP}:9081`, HISTORY_ENDPOINT: '/demo/history', }; export default endpointConfig;
2. 重构拦截器为工具函数
将useSetupInterceptors改为普通工具函数,避免Hook调用限制:
// useSetupInterceptors.js export const setupInterceptors = (axiosInstance, signOut, navigate) => { const interceptor = axiosInstance.interceptors.response.use( (response) => response, (error) => { if (error.response && error.response.status === 403) { console.error('会话失效,请重新登录:', error.message); handleLogout(signOut, navigate); } return Promise.reject(error); } ); const handleLogout = (signOut, navigate) => { signOut(); navigate('/'); }; // 返回移除拦截器的函数,用于组件卸载时清理 return () => { axiosInstance.interceptors.response.eject(interceptor); }; };
3. 在App组件中初始化拦截器
在AuthProvider上下文内调用useSignOut,并设置全局axios拦截器:
// App.js import React, { useEffect } from 'react'; import { BrowserRouter, Route, Routes, useNavigate } from 'react-router-dom'; import { AuthProvider, RequireAuth, useSignOut } from 'react-auth-kit'; import DemoHistory from './components/DemoHistory'; import { createAuthAxios } from './services/EndpointConfig'; import { setupInterceptors } from './util/useSetupInterceptors'; const AppContent = () => { const signOut = useSignOut(); const navigate = useNavigate(); useEffect(() => { const authAxios = createAuthAxios(); const ejectInterceptor = setupInterceptors(authAxios, signOut, navigate); // 将axios实例挂载到全局,方便服务层调用 window.authAxios = authAxios; return () => ejectInterceptor(); }, [signOut, navigate]); return ( <div> <h2 className="header_name">Demo</h2> <BrowserRouter> <Routes> <Route path="/DemoHistory" element={<RequireAuth loginPath="/"><DemoHistory /></RequireAuth>} /> </Routes> </BrowserRouter> </div> ); }; function App() { return ( <AuthProvider authType={"cookie"} authName={"_auth"} cookieDomain={window.location.hostname} cookieSecure={false} > <AppContent /> </AuthProvider> ); } export default App;
4. 修改DemoService使用动态实例
确保每次请求都使用最新的token:
// DemoService.js import endpointConfig from './EndpointConfig'; import { createAuthAxios } from './EndpointConfig'; class DemoService { constructor() { this.getHistoryEndpoint = endpointConfig.HISTORY_ENDPOINT; } async getDemoHistory() { try { const authAxios = createAuthAxios(); const response = await authAxios.get(`${this.getHistoryEndpoint}`); return response.data; } catch (error) { console.error('Error retrieving data:', error); throw error; // 抛出错误让组件处理 } } } export default new DemoService();
二、会话管理优化方案
1. 动态Token获取机制
每次请求前重新从cookie读取token,避免初始化时的token因Java重启、tokenVersion失效而过期。
2. 浏览器关闭时清理会话
监听beforeunload事件,在用户关闭浏览器/标签页时主动清理会话:
// 在AppContent组件中添加 useEffect(() => { const handleBeforeUnload = () => { signOut(); }; window.addEventListener('beforeunload', handleBeforeUnload); return () => { window.removeEventListener('beforeunload', handleBeforeUnload); }; }, [signOut]);
同时可以在后端设置cookie的maxAge属性,确保会话自动过期,双重保障。
3. 403错误体验优化
替换alert为更友好的提示方式,同时清除本地缓存:
// 修改setupInterceptors中的handleLogout const handleLogout = (signOut, navigate) => { // 清除额外本地存储 localStorage.removeItem('userInfo'); signOut(); // 可使用react-toastify等组件展示提示 // toast.error('会话已失效,请重新登录'); setTimeout(() => navigate('/'), 1000); };
三、额外建议
- 避免使用
window.location.href跳转,改用react-router-dom的useNavigate钩子,保持单页应用路由一致性。 - 实现Token自动刷新:在token过期前请求新的token,减少用户登录频率。
- 添加全局错误边界组件,捕获组件树中的错误,提升用户体验。
内容的提问来源于stack exchange,提问作者Harish Mahi
相关产品推荐
相关产品推荐

