React Redux中复用Fetch钩子时如何避免重复发起请求?
问题描述
我编写了一个自定义Hook用于在组件中获取外部资源,该资源通过Ajax请求获取后存入Redux Store。期望实现:若资源未在Store中则自动发起请求,若已存在则直接返回数据。但实际使用时,不同组件调用该Hook会重复发起请求。
已排除StrictMode的影响,当前临时方案是父组件调用useResource(true),子组件调用useResource(false),但希望子组件能独立使用,无需依赖父组件处理资源预加载。
Redux Slice 代码
export const fetchResource = createAsyncThunk( 'slice/resource', async () => { const { data } = await axios.get( 'api.com/resource', ); return data; }, ); const slice = createSlice({ name: 'slice', initialState, reducers: {}, extraReducers: (builder) => { builder .addCase(fetchResource.pending, (state) => { state.resource.status = 'loading'; }) .addCase(fetchResource.fulfilled, (state, action) => { state.resource.status = 'success'; state.resource.data = action.payload; }) .addCase(fetchResource.rejected, (state) => { state.resource.status = 'error'; }); }, });
自定义Hook 代码
export function useResource(initialFetch = false) { const dispatch = useDispatch(); const status = useSelector(state => state.resource.status); const resource = useSelector(state => state.resource.resource); const refreshResource = useCallback(() => { dispatch(fetchResource()); }, [dispatch]); useEffect(() => { if (initialFetch && status === 'idle') refreshResource(); }, [initialFetch, status, refreshResource]); return { status, resource, refreshResource, } }
问题原因
当Parent组件同时挂载Child1和Child2时,两个子组件的useResource(true)会同时执行:
- 初始状态下Store中
resource.status为idle,两个组件的useEffect都会触发refreshResource - Redux的
createAsyncThunk默认不会拦截相同的并发请求,因此会同时发起两次Ajax调用 - 等到第一个请求完成更新状态为
success时,第二个请求已经发出去了,无法取消
修复方案
方案一:优化自定义Hook的触发条件
修改useEffect的判断逻辑,排除loading状态,避免在请求正在进行时发起新请求:
export function useResource(initialFetch = false) { const dispatch = useDispatch(); const status = useSelector(state => state.resource.status); const resource = useSelector(state => state.resource.resource); const refreshResource = useCallback(() => { dispatch(fetchResource()); }, [dispatch]); useEffect(() => { // 仅在需要初始请求、且状态既不是成功也不是加载中时发起请求 if (initialFetch && status !== 'success' && status !== 'loading') { refreshResource(); } }, [initialFetch, status, refreshResource]); return { status, resource, refreshResource, } }
方案二:在Redux层面拦截重复请求
通过createAsyncThunk的condition选项,在发起请求前检查当前状态,拦截重复请求:
export const fetchResource = createAsyncThunk( 'slice/resource', async (_, { getState }) => { const { resource } = getState(); // 若数据已存在或正在加载,直接返回现有数据,不发起新请求 if (resource.status === 'success' || resource.status === 'loading') { return resource.data; } const { data } = await axios.get( 'api.com/resource', ); return data; }, { condition: (_, { getState }) => { const { resource } = getState(); // 仅当状态为idle或error时,允许发起请求 return resource.status === 'idle' || resource.status === 'error'; } } );
这种方法更可靠,直接在Redux层面控制请求发起,避免多组件触发时的重复请求。
方案三:使用RTK Query(推荐)
如果项目使用Redux Toolkit,RTK Query是专门为数据获取设计的工具,内置自动去重缓存、请求取消等功能,无需手动处理重复请求:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; export const resourceApi = createApi({ reducerPath: 'resourceApi', baseQuery: fetchBaseQuery({ baseUrl: 'https://api.com/' }), endpoints: (builder) => ({ getResource: builder.query({ query: () => 'resource', }), }), }); // 生成可直接使用的自定义Hook export const { useGetResourceQuery } = resourceApi;
组件中调用时自动缓存,重复调用不会发起重复请求:
const { data: resource, status } = useGetResourceQuery();
内容的提问来源于stack exchange,提问作者SirRastusBear
相关产品推荐
相关产品推荐

