每 x 秒轮询 API 并做出反应

Polling API every x seconds with react

我必须每隔一两秒在屏幕上监视一些数据更新信息。 我认为使用此实现的方式:

    componentDidMount() {
        this.timer = setInterval(()=> this.getItems(), 1000);
      }
    
      componentWillUnmount() {
        this.timer = null;
      }
    
      getItems() {
        fetch(this.getEndpoint('api url endpoint'))
            .then(result => result.json())
            .then(result => this.setState({ items: result }));
      }

这是正确的方法吗?

好吧,由于您只有一个 API 并且无法控制它以将其更改为使用套接字,因此唯一的方法就是轮询。

根据您的民意测验,您的做法不错。但是上面的代码中有一个问题。

componentDidMount() {
  this.timer = setInterval(()=> this.getItems(), 1000);
}

componentWillUnmount() {
  this.timer = null; // here...
}

getItems() {
  fetch(this.getEndpoint('api url endpoint'))
    .then(result => result.json())
    .then(result => this.setState({ items: result }));
}

这里的问题是,一旦您的组件卸载,尽管您存储在 this.timer 中的对间隔的引用设置为 null,但它还没有停止。即使您的组件已卸载,间隔仍会继续调用处理程序,并会尝试 setState 不再存在的组件。

要正确处理它,请先使用 clearInterval(this.timer),然后设置 this.timer = null

此外,fetch 调用是异步的,这可能会导致同样的问题。制作 cancelable 并在 fetch 不完整时取消。

希望对您有所帮助。

尽管这是一个老问题,但它是我搜索 React Polling 时的最高结果,但没有与 Hooks 兼容的答案。

// utils.js

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

export const useInterval = (callback, delay) => {

  const savedCallback = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);


  useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
      const id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

来源:https://overreacted.io/making-setinterval-declarative-with-react-hooks/

然后您可以直接导入并使用。

// MyPage.js

import useInterval from '../utils';

const MyPage = () => {

  useInterval(() => {
    // put your interval code here.
  }, 1000 * 10);

  return <div>my page content</div>;
}

@AmitJS94,有一个关于如何停止间隔的详细部分,它添加到 GavKilbride 提到的方法上 in this article

作者说要为延迟变量添加一个状态,并在要暂停间隔时为该延迟传入“null”:

const [delay, setDelay] = useState(1000);
const [isRunning, setIsRunning] = useState(true);
  useInterval(() => {
    setCount(count + 1);
  }, isRunning ? delay : null);

    useEffect(() => {
    function tick() {
      savedCallback.current();
    }

    if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);

一定要阅读这篇文章以更好地理解细节——它超级透彻而且写得很好!

您可以使用 setTimeoutclearTimeout 的组合。

setInterval 将每 'x' 秒触发一次 API 调用,而不管之前的调用是成功还是失败。随着时间的推移,这会占用您的浏览器内存并降低性能。而且,如果服务器宕机,setInterval会在不知道服务器宕机状态的情况下继续轰炸。

然而,

您可以使用 setTimeout 进行递归。仅当先前的 API 调用成功时,才触发后续的 API 调用。如果之前的调用失败,清除超时并且不触发任何进一步的调用。如果需要,在失败时提醒用户。让用户刷新页面以重新启动此过程。

这是一个示例代码:

let apiTimeout = setTimeout(fetchAPIData, 1000);

function fetchAPIData(){
    fetch('API_END_POINT')
    .then(res => {
            if(res.statusCode == 200){
                // Process the response and update the view.
                // Recreate a setTimeout API call which will be fired after 1 second.
                apiTimeout = setTimeout(fetchAPIData, 1000);
            }else{
                clearTimeout(apiTimeout);
                // Failure case. If required, alert the user.
            }
    })
    .fail(function(){
         clearTimeout(apiTimeout);
         // Failure case. If required, alert the user.
    });
}

这是一个简单、完整的解决方案,即:

  • 每 X 秒轮询一次

  • 可以选择在每次逻辑运行时增加超时,这样您就不会使服务器过载

  • 清除最终用户退出组件时的超时

     //mount data
     componentDidMount() {
         //run this function to get your data for the first time
         this.getYourData();
         //use the setTimeout to poll continuously, but each time increase the timer
         this.timer = setTimeout(this.timeoutIncreaser, this.timeoutCounter);
     }
    
     //unmounting process
     componentWillUnmount() {
         this.timer = null; //clear variable
         this.timeoutIncreaser = null; //clear function that resets timer
     }
    
     //increase by timeout by certain amount each time this is ran, and call fetchData() to reload screen
     timeoutIncreaser = () => {
         this.timeoutCounter += 1000 * 2; //increase timeout by 2 seconds every time
         this.getYourData(); //this can be any function that you want ran every x seconds
         setTimeout(this.timeoutIncreaser, this.timeoutCounter);
     }
    

正如 Vasanth 所说,我更喜欢:

  • 使用setTimeout测量上一个请求结束和下一个请求开始之间的时间
  • 立即发出第一个请求,不要延迟
  • 灵感来自@KyleMit 的回答
import { useEffect, useRef } from 'react';

export const useInterval = (
  callback: Function,
  fnCondition: Function,
  delay: number,
) => {
  const savedCallback = useRef<Function>();
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);
  useEffect(() => {
    let id: NodeJS.Timeout;
    const tick = async () => {
      try {
        const response =
          typeof savedCallback.current === 'function' &&
          (await savedCallback.current());
        if (fnCondition(response)) {
          id = setTimeout(tick, delay);
        } else {
          clearTimeout(id);
        }
      } catch (e) {
        console.error(e);
      }
    };
    tick();
    return () => id && clearTimeout(id);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [delay]);
};

WORKS: 使用fnCondition,其中可以是基于上次请求响应的条件。

//axios-hooks
const {
    data,
    isLoadingData,
    getData,
} = api.useGetData();

const fnCondition = (result: any) => {
    const randomContidion = Math.random();
    //return true to continue
    return randomContidion < 0.9;
  };
useInterval(() => getData(), fnCondition, 1000);

不起作用:将 delay 作为 null 传递以停止 像这样的 useInterval 对我不起作用 使用此代码:https://www.aaron-powell.com/posts/2019-09-23-recursive-settimeout-with-react-hooks/

(你可能觉得它有效,但在几次 starts/stops 之后它就坏了)

  const [isRunning, setIsRunning] = useState(true);
  const handleOnclick = () => {
    setIsRunning(!isRunning);
  };

  useInterval(() => getData(), isRunning ? 1000 : null);
  <button onClick={handleOnclick}>{isRunning ? 'Stop' : 'Start'}</button>

总结:我可以通过传递 fnCondition 来停止 useInterval,但不能通过传递 delay=null