You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 23:31:38