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

如何避免独立组件同时触发重复数据请求?

解决Redux中同一数据多组件重复请求的问题

你的问题核心在于:两个组件同时发起请求时,Redux状态的更新是同步但有微小延迟的,第二个请求检查状态时,第一个请求的setRequest还未完成状态更新,导致重复请求。以下是几种可行的解决方案:

方案一:使用Redux Toolkit的createAsyncThunk(推荐)

Redux官方推荐的Redux Toolkit(RTK)内置了异步请求的状态管理,能自动处理请求的pending状态,且支持在发起前判断是否需要重复请求:

1. 定义Data Slice

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { get } from './你的API工具';

// 定义异步Thunk,添加请求前置判断
export const fetchData = createAsyncThunk(
  'data/fetchData',
  async (url) => {
    const res = await get(url);
    return res.data;
  },
  {
    condition: (url, { getState }) => {
      const { dataReducer } = getState();
      // 检查该URL是否已有请求在处理中
      const isPending = dataReducer.pendingRequests.includes(url);
      if (isPending) return false; // 已有请求,终止当前请求
    }
  }
);

const dataSlice = createSlice({
  name: 'data',
  initialState: {
    data: {}, // 存储各URL对应的数据
    pendingRequests: [] // 记录正在请求的URL
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchData.pending, (state, action) => {
        state.pendingRequests.push(action.meta.arg);
      })
      .addCase(fetchData.fulfilled, (state, action) => {
        const url = action.meta.arg;
        state.data[url] = action.payload;
        state.pendingRequests = state.pendingRequests.filter(u => u !== url);
      })
      .addCase(fetchData.rejected, (state, action) => {
        const url = action.meta.arg;
        state.pendingRequests = state.pendingRequests.filter(u => u !== url);
      });
  }
});

export default dataSlice.reducer;

2. 组件中使用

import { useDispatch, useSelector } from 'react-redux';
import { fetchData } from './dataSlice';
import { useEffect } from 'react';

function TargetComponent({ url }) {
  const dispatch = useDispatch();
  const data = useSelector(state => state.dataReducer.data[url]);
  const isLoading = useSelector(state => state.dataReducer.pendingRequests.includes(url));

  useEffect(() => {
    if (!data && !isLoading) {
      dispatch(fetchData(url));
    }
  }, [url, data, isLoading, dispatch]);

  // 渲染逻辑
  if (isLoading) return <div>加载中...</div>;
  return <div>{JSON.stringify(data)}</div>;
}

方案二:手动封装内存锁(无需RTK)

如果不使用RTK,可以用内存中的Set做即时请求锁(同步更新,比Redux状态更快),结合Redux状态处理页面刷新后的情况:

1. 封装请求Thunk

import { get } from './你的API工具';

// 内存中存储正在请求的URL,同步更新
const pendingRequests = new Set();

export function fetchData(url) {
  return async (dispatch, getState) => {
    // 先检查内存锁,避免同时发起的请求
    if (pendingRequests.has(url)) return;
    // 再检查Redux状态,防止页面刷新后内存锁丢失
    const state = getState();
    if (state.dataReducer.pendingRequests.includes(url)) return;

    pendingRequests.add(url);
    dispatch({ type: 'SET_REQUEST', payload: url });

    try {
      const res = await get(url);
      dispatch({ type: 'SET_DATA', payload: { url, data: res.data } });
    } catch (err) {
      dispatch({ type: 'SET_ERROR', payload: { url, error: err } });
    } finally {
      pendingRequests.delete(url);
      dispatch({ type: 'UNSET_REQUEST', payload: url });
    }
  };
}

2. 组件中调用

useEffect(() => {
  dispatch(fetchData(url));
}, [url, dispatch]);

方案三:封装自定义Hook统一处理

将数据请求逻辑封装成自定义Hook,结合useRef记录已发起请求的状态,避免同一URL重复触发:

import { useDispatch, useSelector } from 'react-redux';
import { useEffect, useRef } from 'react';
import { fetchData } from './你的action';

export function useData(url) {
  const dispatch = useDispatch();
  const data = useSelector(state => state.dataReducer.data[url]);
  const isLoading = useSelector(state => state.dataReducer.pendingRequests.includes(url));
  const hasInitiated = useRef(false);

  useEffect(() => {
    if (!hasInitiated.current && !data && !isLoading) {
      hasInitiated.current = true;
      dispatch(fetchData(url));
    }
  }, [url, data, isLoading, dispatch]);

  return { data, isLoading };
}

组件中直接使用:

const { data, isLoading } = useData(targetUrl);

内容的提问来源于stack exchange,提问作者ddominoo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 02:52:38