React+Vite中BrowserRouter引发HTTP请求重复触发问题求助
React HTTP请求重复触发问题排查与解决
问题描述
并非React专家,当前遇到HTTP请求重复触发的问题,通过React Profiler发现是BrowserRouter发起了多次调用,不清楚为何会出现这类重复请求。后端使用Django,相关HTTP请求日志、app.tsx及ViewDashboard.tsx代码如下:
HTTP请求日志
System check identified no issues (0 silenced). August 29, 2023 - 10:57:35 Django version 4.2.3, using settings 'REDACTED.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CTRL-BREAK. [29/Aug/2023 10:57:37] "OPTIONS /users/api/v1/token/ HTTP/1.1" 200 0 [29/Aug/2023 10:57:38] "POST /users/api/v1/token/ HTTP/1.1" 200 483 [29/Aug/2023 10:57:38] "OPTIONS /users/api/v1/token/refresh/ HTTP/1.1" 200 0 [29/Aug/2023 10:57:38] "POST /users/api/v1/token/refresh/ HTTP/1.1" 200 483 [29/Aug/2023 10:57:38] "OPTIONS /users/api/v1/token/refresh/ HTTP/1.1" 200 0 [29/Aug/2023 10:57:38] "POST /users/api/v1/token/refresh/ HTTP/1.1" 200 483 [29/Aug/2023 10:57:38] "OPTIONS /dashboards/api/v1/dashboards/ HTTP/1.1" 200 0 [29/Aug/2023 10:57:38] "OPTIONS /dashboards/api/v1/dashboards/ HTTP/1.1" 200 0 [29/Aug/2023 10:57:38] "GET /dashboards/api/v1/dashboards/ HTTP/1.1" 200 146 [29/Aug/2023 10:57:38] "OPTIONS /users/api/v1/token/refresh/ HTTP/1.1" 200 0 [29/Aug/2023 10:57:38] "OPTIONS /users/api/v1/token/refresh/ HTTP/1.1" 200 0 [29/Aug/2023 10:57:38] "POST /users/api/v1/token/refresh/ HTTP/1.1" 200 483 [29/Aug/2023 10:57:38] "POST /users/api/v1/token/refresh/ HTTP/1.1" 200 483 [29/Aug/2023 10:57:38] "OPTIONS /dashboards/api/v1/embed/REDACTED/REDACTED/ HTTP/1.1" 200 0 [29/Aug/2023 10:57:38] "OPTIONS /dashboards/api/v1/embed/REDACTED/REDACTED/ HTTP/1.1" 200 0 [29/Aug/2023 10:57:39] "GET /dashboards/api/v1/dashboards/ HTTP/1.1" 200 146 [29/Aug/2023 10:57:40] "GET /dashboards/api/v1/embed/REDACTED/REDACTED/ HTTP/1.1" 200 2555 [29/Aug/2023 10:57:40] "GET /dashboards/api/v1/embed/REDACTED/REDACTED/ HTTP/1.1" 200 2555
app.tsx代码
import React from 'react'; import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; import './App.css'; import Login from './Login'; import SelectDashboard from './SelectDashboard'; import ViewDashboard from './ViewDashboard'; const App: React.FC = () => { return ( <Router> <Routes> <Route path="/login" element={<Login />} /> <Route path="/" element={<Login />} /> <Route path="/select-dashboard" element={<SelectDashboard />} /> <Route path="/dashboard" element={<ViewDashboard />} /> <Route path="*" element={<Navigate to="/" replace />} /> </Routes> </Router> ); }; export default App;
ViewDashboard.tsx代码
import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import * as pbi from 'powerbi-client'; import './App.css'; const ViewDashboard: React.FC = () => { const navigate = useNavigate(); const [embedData, setEmbedData] = useState<any | null>(null); const handleLogout = () => { // Clear localStorage items localStorage.removeItem('refresh'); localStorage.removeItem('selectedWorkspaceId'); localStorage.removeItem('selectedReportId'); navigate('/login'); }; const getEmbed = async () => { // Try to get the refresh token from local storage try { const refresh = localStorage.getItem('refresh') || ''; const refreshRequestBody = JSON.stringify({ refresh }); // Get a new access token const newTokenResponse = await fetch('http://localhost:8000/users/api/v1/token/refresh/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: refreshRequestBody, }); const newToken = await newTokenResponse.json(); localStorage.setItem('jwt', newToken.access); const response = await fetch( 'http://localhost:8000/dashboards/api/v1/embed/' + localStorage.getItem('selectedWorkspaceId') + '/' + localStorage.getItem('selectedReportId') + '/', { method: 'GET', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + localStorage.getItem('jwt'), }, } ); const data = await response.json(); return data; } catch (error) { redirect('/login'); } }; const redirect = (path: string) => { navigate(path); }; const embedDashboard = async () => { try { const data = await getEmbed(); setEmbedData(data); } catch (error) { throw error; } }; useEffect(() => { embedDashboard(); }, []); // Only run once when the component mounts useEffect(() => { if (embedData) { // Embed the Power BI dashboard here const config: pbi.IEmbedConfiguration = { type: 'report', id: embedData.reports[0].id, embedUrl: embedData.reports[0].embedUrl, accessToken: embedData.token, tokenType: pbi.models.TokenType.Embed, }; const embedContainer = document.querySelector('.report-div'); if (embedContainer) { // @ts-ignore const dashboard = powerbi.embed(embedContainer, config); dashboard.off('loaded'); // Prevent any additional event listeners } } }, [embedData]); return ( <> <div className="horizontal-bar"> <button onClick={handleLogout} className="logout-button"> Logout </button> </div> <div className="dashboard-container"> <div className="report-div"></div> </div> </> ); }; export default ViewDashboard;
问题原因与解决方案
核心原因
- React StrictMode 双重调用:开发环境下,StrictMode会主动双重调用
useEffect、useState初始化等方法,目的是检测潜在的副作用问题。你的初始化请求正好在这个范围内,所以被触发两次。 - 组件重复挂载:检查是否存在路由配置错误,导致
ViewDashboard被多次渲染;或者SelectDashboard中跳转逻辑有重复触发的情况。
解决步骤
- 验证StrictMode影响:打开
index.tsx,如果存在<React.StrictMode>包裹<App />,暂时注释后重新运行项目。如果请求不再重复,说明是StrictMode的开发模式特性,生产环境会自动恢复正常,无需修改代码。 - 添加请求取消机制:即使是开发模式,也可以通过
AbortController避免无效的重复请求,修改ViewDashboard.tsx代码如下:
// 修改useEffect,添加AbortController useEffect(() => { const abortController = new AbortController(); const embedDashboard = async () => { try { const data = await getEmbed(abortController.signal); setEmbedData(data); } catch (error) { if ((error as Error).name !== 'AbortError') { redirect('/login'); } } }; embedDashboard(); return () => abortController.abort(); // 组件卸载或重复执行时取消请求 }, []); // 修改getEmbed函数,接收signal参数 const getEmbed = async (signal: AbortSignal) => { try { const refresh = localStorage.getItem('refresh') || ''; const refreshRequestBody = JSON.stringify({ refresh }); const newTokenResponse = await fetch('http://localhost:8000/users/api/v1/token/refresh/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: refreshRequestBody, signal, // 传入取消信号 }); const newToken = await newTokenResponse.json(); localStorage.setItem('jwt', newToken.access); const response = await fetch( 'http://localhost:8000/dashboards/api/v1/embed/' + localStorage.getItem('selectedWorkspaceId') + '/' + localStorage.getItem('selectedReportId') + '/', { method: 'GET', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + localStorage.getItem('jwt'), }, signal, // 传入取消信号 } ); const data = await response.json(); return data; } catch (error) { if ((error as Error).name !== 'AbortError') { redirect('/login'); } throw error; } };
- 检查路由跳转逻辑:确认
SelectDashboard组件中跳转到/dashboard的代码是否只执行一次,比如是否存在点击事件重复绑定、状态更新导致重复跳转的情况。
内容的提问来源于stack exchange,提问作者holyshilo111
相关产品推荐
相关产品推荐

