如何管理多个React组件的异步请求?

新手上路,请多包涵

React中组件A里有个获取配置数据的异步请求,列表中多个组件A就会触发多个相同的异步请求,期望是等到第一个异步请求回来进行数据缓存,剩下组件直接读取缓存来获取配置数据。

我想了一下,使用axios的拦截器把请求拦截下来处理,但是如何让剩下的组件进行等待呢?或者有什么其他思路?

阅读 1.7k
avatarAI BotBETA

这个问题可以通过使用React的状态钩子(useState)和副作用钩子(useEffect)来解决。

首先,我们需要在组件A中创建一个状态来存储配置数据。然后,我们使用useEffect钩子来处理异步请求。当组件挂载时,我们发送异步请求并更新状态。当组件卸载时,我们清除所有的定时器或订阅。

下面是一个简单的例子:

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function ComponentA() {
  const [configData, setConfigData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await axios.get('your-api-url');
        setConfigData(response.data);
      } catch (error) {
        console.error(error);
      }
    };

    fetchData();
  }, []); // 注意,这里依赖项数组为空,这意味着这个effect只在组件挂载和卸载时运行。

  // 在这里,你可以根据需要返回一个loading状态或者一个错误消息,直到数据被成功获取。
  if (!configData) {
    return <div>Loading...</div>;
  }

  return <div>{configData}</div>;
}

在这个例子中,每次组件A挂载时,都会发送一个异步请求来获取配置数据。然后,这个数据会被存储在状态中,所以其他组件可以访问它。当组件A卸载时,所有的异步请求都会被清除。这样,如果其他组件也使用了相同的配置数据,它们可以直接从状态中读取数据,而不需要再次发送请求。

此外,你也可以使用更复杂的逻辑来处理多个异步请求,例如使用Promise.all来等待所有的请求都完成。但是,这需要更复杂的代码,而且可能会导致其他组件在数据还未完全加载时就被渲染出来。因此,这个简单的例子应该足以满足你的需求。

4 个回答

正好昨天处理了一个类似的问题,按理说这种公共组件最好别有请求,但团队里其他人已经这么写了,而且代码已经测试过了,所以不好大改。
我这边是用一个单例去保存每次请求的结果,最后一段代码里的1000代表缓存的超时时间为1000ms(1s),如果在1s内一个请求再次发起,如果这个请求和上次请求的参数一致,就使用上次的缓存,当然如果上次请求还在pending,也不会重新发起,会一并等上次的结果。具体超时时间设多少看接口的特性,数据不会频繁变的话,设十几二十秒也可以。
最后用这个hook返回的apiCall去替换之前的请求方法就行。

import { useCallback, useState } from "react";
import { isEqual } from "lodash";

interface ResponseInfo {
  params: unknown[];
  lastRequestTime: number;
  getData: () => Promise<unknown>;
}

type PromiseValue<T> = T extends Promise<infer U> ? U : T;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type CommonFunc = (...p: any[]) => any;

type ResponseData<T> = {
  data: T;
  isSuccess: boolean;
  isError: boolean;
};

type ResponseValue<F extends CommonFunc> = {
  apiCall: F;
  data: ResponseData<PromiseValue<ReturnType<F>>>;
  loading: boolean;
};

const responseWeakMap = new WeakMap<CommonFunc, ResponseInfo>();

const genUseSingletonApi =
  <T extends CommonFunc>(apiCall: T) =>
  (instance: CommonFunc, expiration: number) =>
  () => {
    type APIReturnData = PromiseValue<ReturnType<typeof apiCall>>;
    const [data, setData] = useState<APIReturnData>();
    const [loading, setLoading] = useState<boolean>();
    const apiCallFunc = useCallback(
      async (...params: Parameters<T>) => {
        setLoading(true);
        const endCallback = (res: APIReturnData) => {
          setLoading(false);
          setData(res);
        };
        const requestInfo = responseWeakMap.get(instance);
        if (requestInfo) {
          const { getData: getResponseInfo, params: oldParams, lastRequestTime } = requestInfo;
          const hasResponse =
            isEqual(oldParams, params) && +new Date() - lastRequestTime <= expiration;
          if (hasResponse) {
            const res = await getResponseInfo();
            endCallback(res as APIReturnData);
            return res;
          }
        }
        let resolve: (value: APIReturnData) => void = () => {};
        const promise: Promise<APIReturnData> = new Promise(resolveCb => {
          resolve = resolveCb;
        });
        responseWeakMap.set(instance, {
          getData: () => promise,
          params,
          lastRequestTime: +new Date(),
        });
        const result = await apiCall(...params);
        resolve(result);
        endCallback(result);
        return result;
      },
      [],
    );

    return {
      apiCall: apiCallFunc,
      data,
      loading,
    };
  };

const someAPISingletonInstance = genUseSingletonApi(fetchDataFunc);

export const useAccountingList = someAPISingletonInstance(
  someAPISingletonInstance,
  1000,
);

直接使用 @tanstack/react-query

按照题目,意思是有个组件A内置一个获取异步请求的参数,然后恰好有一个页面会实例化多个组件A导致出现多次完全重复的异步请求

优化方案我觉得无论怎么都是要将异步请求给抽离出来的

具体抽离用什么方法,用类似Vuex-Store(状态管理)这种全局的实例去做管理又或者是像第一个回答的哥们那样用hook去写都是可以的,但是前提我觉得都是要把异步请求给抽离出来

一般来说都是父组件提供数据,子组件只负责接收数据并渲染即可

所以给我来改就会改动组件A代码,让其接收从外部传入的数据,并提供接额外口关闭自身的异步请求(如果需要兼容的话)

新手上路,请多包涵

useSWR/react-query 或者其他管理请求的库

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题