如何避免useEffect中dispatch多个Redux异步Action重复发起网络请求?
解决Redux createAsyncThunk重复网络请求问题
你的代码中,Redux的useDispatch返回的dispatch是稳定引用,理论上依赖[dispatch]的useEffect应该只在组件挂载时执行一次。出现多次触发的原因通常是组件被重复挂载/卸载,或者父组件状态变化导致不必要的重渲染。以下是几种可行的解决方法:
方法一:优化组件渲染,避免不必要重渲染
用React.memo包裹组件,通过浅比较props来阻止父组件无关状态变化引发的子组件重复渲染:
import { memo } from 'react'; import { useDispatch } from 'react-redux'; const Dashboard = memo(() => { const dispatch = useDispatch(); useEffect(() => { function initDashboardData() { console.count("initDashboardData"); void dispatch(getDatabasesList()); void dispatch(getUserConfig()); void dispatch( getHistoricalData({ startTimestamp: getDateXDaysAgo(14, new Date()), endTimestamp: new Date() }) ); } void initDashboardData(); }, [dispatch]); // 组件渲染内容 }); export default Dashboard;
方法二:在Redux层面拦截重复请求
在createAsyncThunk内部结合slice的加载状态,判断是否已有正在进行的请求,避免重复调用API:
// 对应slice的状态定义 const initialState = { databasesList: [], isDatabasesLoading: false, userConfig: null, isUserConfigLoading: false, historicalData: null, isHistoricalDataLoading: false, }; const dashboardSlice = createSlice({ name: 'dashboard', initialState, reducers: {}, extraReducers: (builder) => { builder // 处理getDatabasesList的状态 .addCase(getDatabasesList.pending, (state) => { state.isDatabasesLoading = true; }) .addCase(getDatabasesList.fulfilled, (state, action) => { state.databasesList = action.payload; state.isDatabasesLoading = false; }) .addCase(getDatabasesList.rejected, (state) => { state.isDatabasesLoading = false; }) // 同理处理getUserConfig的状态 .addCase(getUserConfig.pending, (state) => { state.isUserConfigLoading = true; }) .addCase(getUserConfig.fulfilled, (state, action) => { state.userConfig = action.payload; state.isUserConfigLoading = false; }) .addCase(getUserConfig.rejected, (state) => { state.isUserConfigLoading = false; }); }, }); // 修改createAsyncThunk逻辑,添加重复请求判断 export const getDatabasesList = createAsyncThunk( 'dashboard/getDatabasesList', async (_, { getState }) => { const { dashboard } = getState(); // 如果正在加载,直接返回,不发起新请求 if (dashboard.isDatabasesLoading) return; const response = await api.get('/databases'); return response.data; } ); // getUserConfig做同样的修改 export const getUserConfig = createAsyncThunk( 'dashboard/getUserConfig', async (_, { getState }) => { const { dashboard } = getState(); if (dashboard.isUserConfigLoading) return; const response = await api.get('/user-config'); return response.data; } );
方法三:用Ref标记初始化状态
在组件内部通过useRef存储标记,确保初始化逻辑只执行一次:
import { useRef, useEffect } from 'react'; import { useDispatch } from 'react-redux'; const Dashboard = () => { const dispatch = useDispatch(); const hasInitialized = useRef(false); useEffect(() => { if (!hasInitialized.current) { const initDashboardData = () => { console.count("initDashboardData"); void dispatch(getDatabasesList()); void dispatch(getUserConfig()); void dispatch( getHistoricalData({ startTimestamp: getDateXDaysAgo(14, new Date()), endTimestamp: new Date() }) ); }; initDashboardData(); hasInitialized.current = true; } }, [dispatch]); // 组件渲染内容 };
该方法强制初始化逻辑仅执行一次,即使组件重复渲染也不会再次触发。若组件卸载后重新挂载,ref会重置,此时会再次执行初始化。
总结
优先通过React.memo优化组件渲染(方法一),从根源减少不必要的effect触发;若组件无法避免重复渲染,可选择用useRef标记(方法三);如果需要全局层面拦截重复请求,推荐在Redux的thunk逻辑中处理(方法二)。
内容的提问来源于stack exchange,提问作者GN.
相关产品推荐
相关产品推荐

