每 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]);
一定要阅读这篇文章以更好地理解细节——它超级透彻而且写得很好!
您可以使用 setTimeout
和 clearTimeout
的组合。
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
我必须每隔一两秒在屏幕上监视一些数据更新信息。 我认为使用此实现的方式:
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]);
一定要阅读这篇文章以更好地理解细节——它超级透彻而且写得很好!
您可以使用 setTimeout
和 clearTimeout
的组合。
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