删除 duplicate/redundant API 请求的最佳做法是什么?

What is best practice to remove duplicate/redundant API requests?

所以我在一个 React 平台上工作,该平台的数据每秒更新一次(我想转移到网络套接字,但它目前只支持获取)。目前,每个组件都会为自己发出获取请求以获取小部件的数据。由于提取请求内置于小部件中,因此存在对相同数据的冗余 api 请求。我正在寻找一个可能更好的解决方案来删除这些冗余 api 请求。

我想出的解决方案使用了我称之为数据服务的东西,该服务检查对数据源的任何订阅,然后进行这些 api 调用,并将数据置于 redux 状态,供组件使用.我不确定这是否是处理我试图避免的问题的最佳方式。我不喜欢我需要一个间隔 运行 应用程序每秒 运行 检查是否有“订阅”。我不确定这是否是正确的方法。使用此解决方案,我不会重复任何请求,并且可以在不影响其他组件的情况下添加或删除订阅。

还有一件事,id 可以改变并且会改变我收到的数据

这是我如何处理服务的简化版本。

const reduxState = {
 id: "specific-id",  
 subscriptions: {
    sourceOne: ["source-1-id-1", "source-1-id-2", "source-1-id-3"],
    sourceTwo: ["source-2-id-1", "source-one-id-2"],
  },
  data: {
    sourceOne: { id: "specific-id", time: "milliseconds", data: "apidata" },
    sourceTwo: { id: "specific-id", time: "milliseconds", data: "apidata" },
  },
};

const getState = () => reduxState; //Would be a dispatch to always get current redux state

const dataService = () => {
  const interval = setInterval(() => {
    const state = getState();
    if (state.subscriptions.sourceOne.length > 0)
      fetchSourcOneAndStoreInRedux();
    if (state.subscriptions.sourceTwo.length > 0)
      fetchSourceTwoAndStoreInRedux();
  }, 1000);
};

const fetchSourcOneAndStoreInRedux = (id) =>{
    return async dispatch => {
        try {
            const res = await axios.get(`/data/one/${id}`) 
            dispatch(setSourceOneDataRedux(res.data))
        } catch (err) {
            console.error(err)
        }
    }
}

我正在构建我的组件以仅显示来自正确 ID 的数据。

这是一个简单的“DataManager”的简单工作示例,它可以实现您正在寻找的功能。

class DataManager {
  constructor(config = {}) {
    this.config = config;
    console.log(`DataManager: Endpoint "${this.config.endpoint}" initialized.`);
    if (this.config.autostart) { // Autostart the manager if autostart property is true
      this.start();
    }
  }

  config; // The config object passed to the constructor when initialized
  fetchInterval; // The reference to the interval function that fetches the data
  data; // Make sure you make this state object observable via MOBX, Redux etc so your component will re-render when data changes.
  fetching = false; // Boolean indicating if the APIManager is in the process of fetching data (prevent overlapping requests if response is slow from server)

  // Can be used to update the frequency the data is being fetched after the class has been instantiated
  // If interval already has been started, stop it and update it with the new interval frequency and start the interval again
  updateInterval = (ms) => {
    if (this.fetchInterval) {
      this.stop();
      console.log(`DataManager: Updating interval to ${ms} for endpoint ${this.config.endpoint}.`);
      this.config.interval = ms;
      this.start();
    } else {
      this.config.interval = ms;
    }
    return this;
  }

  // Start the interval function that polls the endpoint
  start = () => {
    if (this.fetchInterval) {
      clearInterval(this.fetchInterval);
      console.log(`DataManager: Already running! Clearing interval so it can be restarted.`);
    }

    this.fetchInterval = setInterval(async () => {
      if (!this.fetching) {
        console.log(`DataManager: Fetching data for endpoint "${this.config.endpoint}".`);
        this.fetching = true;
        // const res = await axios.get(this.config.endpoint); 
        // Commented out for demo purposes but you would uncomment this and clear the anonymous function below
        const res = {};
        (() => {
          res.data = {
            dataProp1: 1234,
            dataProp2: 4567
          }
        })();
        this.fetching = false;
        this.data = res.data;
      } else {
        console.log(`DataManager: Waiting for pending response for endpoint "${this.config.endpoint}".`);
      }
    }, this.config.interval);

    return this;
  }

  // Stop the interval function that polls the endpoint
  stop = () => {
    if (this.fetchInterval) {
      clearInterval(this.fetchInterval);
      console.log(`DataManager: Endpoint "${this.config.endpoint}" stopped.`);
    } else {
      console.log(`DataManager: Nothing to stop for endpoint "${this.config.endpoint}".`);
    }
    return this;
  }

}

const SharedComponentState = {
  source1: new DataManager({
    interval: 1000,
    endpoint: `/data/one/someId`,
    autostart: true
  }),
  source2: new DataManager({
    interval: 5000,
    endpoint: `/data/two/someId`,
    autostart: true
  }),
  source3: new DataManager({
    interval: 10000,
    endpoint: `/data/three/someId`,
    autostart: true
  })
};

setTimeout(() => { // For Demo Purposes, Stopping and starting DataManager.
  SharedComponentState.source1.stop();
  SharedComponentState.source1.updateInterval(2000);
  SharedComponentState.source1.start();
}, 10000);

// Heres what it would look like to access the DataManager data (fetched from the api)
// You will need to make sure you pass the SharedComponentState object as a prop to the components or use another React mechanism for making that SharedComponentState accessible to the components in your app
// Accessing state for source 1: SharedComponentState.source1.data
// Accessing state for source 2: SharedComponentState.source2.data
// Accessing state for source 3: SharedComponentState.source3.data

基本上,DataManager class 的每个实例都负责获取不同的 api 端点。我包括了一些其他 class 方法,允许您启动、停止和更新 DataManager 实例的轮询频率。