React Protected Route鉴权路由重复调用checkauth接口问题排查
问题根因
你遇到的重复请求和路由打包逻辑无关,核心是以下3个代码问题导致:
- 鉴权请求未做副作用隔离:你直接把axios请求写在了ProtectedRoute的组件函数体中,没有包裹在
useEffect钩子内。每次组件渲染都会执行一次请求,而请求返回后调用setUser、setIsAuthenticated、setGotResponse都会触发组件重渲染,进而再触发新的请求,形成无限循环。 - 多实例重复触发:你在App.js中声明了4个
ProtectedRoute组件,每个都是独立的实例,初始化阶段就会各自触发一次鉴权请求,叠加循环逻辑后请求量会指数级上升。 - ContextProvider位置错误:你把
UserContext.Provider放在了Switch内部,每次user状态更新时,Provider的value变化会触发所有子路由组件全量重渲染,进一步放大请求次数。
修复方案
按以下步骤调整即可解决:
- 给鉴权请求加
useEffect隔离,保证仅全局触发一次请求,同时添加请求取消逻辑避免内存泄漏 - 把鉴权状态提升到App层统一管理,所有ProtectedRoute共享全局鉴权状态,避免多实例重复发请求
- 把
UserContext.Provider移到Switch外层,避免不必要的子组件重渲染
调整后的代码示例
1. 修改ProtectedRoute.js
import React, { useContext } from 'react'; import { Route, Redirect } from 'react-router-dom'; import { UserContext } from '../functions/UserContext'; const ProtectedRoute = ({ component: Component, ...rest }) => { const { isAuthenticated, gotResponse } = useContext(UserContext); if (gotResponse === 1) { return ( <Route {...rest} render={(props) => { if (isAuthenticated === 1) { return <Component {...rest} {...props} />; } else { return ( <Redirect to={{ pathname: '/login', state: { from: props.location, }, }} /> ); } }} /> ); } else { return null; } }; export default ProtectedRoute;
2. 修改App.js
import axios from 'axios'; import { useState, useEffect } from 'react'; import { UserContext } from '../functions/UserContext'; function App() { const [user, setUser] = useState(); const [isAuthenticated, setIsAuthenticated] = useState(0); const [gotResponse, setGotResponse] = useState(0); // 全局仅执行一次鉴权请求 useEffect(() => { const source = axios.CancelToken.source(); axios .post( process.env.REACT_APP_API_URL + '/api/auth/checkauth/', { content: 'checkAuthStatus' }, { headers: { 'Content-Type': 'application/json' }, withCredentials: true, cancelToken: source.token } ) .then((res) => { setUser(res.data.firstName + ' ' + res.data.lastName); setIsAuthenticated(1); setGotResponse(1); }) .catch(function (error) { if (!axios.isCancel(error)) { setIsAuthenticated(0); setGotResponse(1); } }); // 组件卸载时取消未完成的请求 return () => source.cancel(); }, []); return ( <UserContext.Provider value={{ user, setUser, isAuthenticated, gotResponse }}> <main> <Switch> <Route exact path="/login" render={(props) => <Login {...props} />} ></Route> <ProtectedRoute exact path="/account" component={Account} title="Account" ></ProtectedRoute> <ProtectedRoute exact path="/staff" component={Staff} ></ProtectedRoute> <ProtectedRoute exact path="/security" component={Security} ></ProtectedRoute> <ProtectedRoute exact path="/" component={Home}></ProtectedRoute> <Route exact path="/2fa" component={Tfa}></Route> <Route component={NotFound}></Route> </Switch> </main> </UserContext.Provider> ); } export default App;
内容的提问来源于stack exchange,提问作者Sam Clay
相关产品推荐
相关产品推荐

