React中useEffect多次调用引发API重复请求问题排查
问题分析与解决方案
1. 最核心的原因:React 18 开发环境的 StrictMode
React 18 在开发模式下的StrictMode会刻意重复调用组件渲染、useEffect等方法,目的是帮你提前排查潜在的副作用问题(比如未清理的定时器、重复订阅)。这就是你看到组件渲染5-6次、useEffect执行2次的主要原因——生产环境下不会出现这个行为。
你可以打开index.js核对,代码大概率是这样的:
ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> <App /> </React.StrictMode> );
如果想临时验证这个结论,可以暂时去掉<React.StrictMode>标签,开发环境下useEffect就只会执行一次了。不过不建议长期移除,StrictMode能帮你发现很多隐藏问题。
2. 解决API重复请求的问题
哪怕StrictMode导致useEffect执行两次,我们也能通过逻辑控制让API只发起一次,避免重复查询数据库:
方案一:在Redux Action里做前置判断
修改fetchSeries action,先检查Redux中是否已有目标数据,有就直接返回,不发起请求:
// SeriesActions.js export const fetchSeries = (userId, companyId) => async (dispatch, getState) => { // 先获取当前状态中的剧集数据 const existingSeries = getState().seriesList.series; // 如果已有有效数据,直接终止逻辑 if (existingSeries && existingSeries.length > 0) { return; } // 无数据时再发起API请求 try { const res = await axios.get(`/api/series?userId=${userId}&companyId=${companyId}`); dispatch({ type: 'FETCH_SERIES_SUCCESS', payload: res.data }); } catch (err) { dispatch({ type: 'FETCH_SERIES_FAILURE', payload: err.message }); } };
方案二:在组件内加请求标记
在组件里新增一个状态,标记是否已经发起过请求,避免重复触发:
const MyList = () => { const [isSeriesOpen, setIsSeriesOpen] = useState(false); const [hasRequested, setHasRequested] = useState(false); // 新增请求标记 const dispatch = useDispatch(); const series = useSelector((state) => state.seriesList.series); const userId = useSelector((state) => state.auth.user_id); const companyId = 3; console.log("Log from the component"); useEffect(() => { // 已请求过或userId不存在时,直接返回 if (hasRequested || !userId) return; dispatch(fetchSeries(userId, companyId)); setHasRequested(true); console.log("Log for Effect called"); }, [dispatch, userId, hasRequested]); // 补充依赖项 return ( <div> <p> Test Content</p> </div> ); };
3. 修复useEffect的依赖项漏洞
你当前的useEffect依赖数组只有dispatch,但回调里用到了userId和companyId——这会导致闭包问题:比如userId初始是undefined(用户未登录时),后来变为真实值,useEffect不会重新执行,请求会用旧的userId发起。
必须把用到的变量都加入依赖数组:
useEffect(() => { // 先判断userId是否有效,避免无效请求 if (!userId) return; dispatch(fetchSeries(userId, companyId)); console.log("Log for Effect called"); }, [dispatch, userId, companyId]); // 补充缺失的依赖
4. 关于组件多次渲染
组件渲染5-6次是正常现象,触发场景包括:
- 组件初始渲染
userId从undefined变为真实值时的重渲染fetchSeries请求完成后,Redux的seriesList.series更新触发的重渲染- 父组件状态变化带来的子组件重渲染
如果想减少不必要的重渲染,可以用React.memo包裹组件,或者在useSelector里启用浅比较:
// 导入浅比较工具 import { shallowEqual } from 'react-redux'; // 使用浅比较避免无意义重渲染 const series = useSelector((state) => state.seriesList.series, shallowEqual); const userId = useSelector((state) => state.auth.user_id, shallowEqual); // 用React.memo包裹组件,减少父组件无关变化导致的重渲染 export default React.memo(MyList);
内容的提问来源于stack exchange,提问作者user2742581
相关产品推荐
相关产品推荐

