我必须每隔一两秒钟在屏幕上监控一些数据更新信息。 我认为使用此实现的方式是:
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 }));
}
这是正确的方法吗?
答案 0 :(得分:21)
好吧,由于您只有一个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
不完整,则取消。
我希望这会有所帮助。
答案 1 :(得分:14)
尽管有一个古老的问题,但当我搜索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>;
}
答案 2 :(得分:2)
@ AmitJS94,其中有一个有关如何停止间隔的详细部分,该间隔添加到GavKilbride提到的in this article方法上。
作者说要为延迟变量添加一个状态,并在您要暂停间隔时为该延迟传递“ null”:
import React, {
useEffect, useContext, useState, useRef,
} from 'react';
import PropTypes from 'prop-types';
import Player from '@vimeo/player';
import { BrowserContext } from '../../contexts/BrowserContext';
const TAG = 'player';
/**
* remove event listeners
* @param {object} player
*/
function removeEventListeners(player) {
if (!player) return;
player.off('ended');
player.off('pause');
player.off('play');
}
/**
* remove interval
* @param {number} interval
*/
function removeInterval(interval) {
console.tag(TAG).debug('removeInterval called');
window.clearInterval(interval);
}
/**
* 640×480, 800×600, 960×720, 1024×768, 1280×960,
* 1400×1050, 1440×1080 , 1600×1200, 1856×1392, 1920×1440, and 2048×1536
* @param {number} width
*/
function computeRatio(delayedWidth, percentage = 0.9) {
const height = window.innerHeight;
const width = delayedWidth - (delayedWidth * (1 - percentage));
if (height <= 480) {
return width > 640 ? 640 : width;
}
if (height <= 600) {
return width > 800 ? 800 : width;
}
if (height <= 720) {
return width > 960 ? 960 : width;
}
if (height <= 768) {
return width > 1024 ? 1024 : width;
}
if (height <= 960) {
return width > 1280 ? 1280 : width;
}
if (height <= 1050) {
return width > 1400 ? 1400 : width;
}
if (height <= 1080) {
return width > 1440 ? 1440 : width;
}
if (height <= 1200) {
return width > 1600 ? 1600 : width;
}
if (height <= 1392) {
return width > 1856 ? 1856 : width;
}
if (height <= 1440) {
return width > 1920 ? 1920 : width;
}
if (height <= 1536) {
return width > 2048 ? 2048 : width;
}
return width;
}
const VideoPlayer = ({
index, link, onProgress, latestProgress, widthPercentage, onVideoEnded,
}) => {
const { delayedWidth } = useContext(BrowserContext);
const [progress, setProgress] = useState(latestProgress < 1 ? latestProgress : 0);
const playerRef = useRef(null);
const intervalRef = useRef(null);
useEffect(() => {
console.tag(TAG).debug('changing delayed width', delayedWidth);
const asyncEffect = async () => {
const player = playerRef.current;
if (player) {
console.tag(TAG).debug('player detected, checking fullscreen');
const isFullscreen = await player.getFullscreen();
console.tag(TAG).debug('fullscreen detected', isFullscreen);
if (isFullscreen) {
return;
}
removeEventListeners(player);
playerRef.current = null;
player.pause(); // gets rid of interval
player.destroy();
}
const options = { id: link, width: computeRatio(delayedWidth, widthPercentage) };
const newPlayer = new Player(`frame-${index}`, options);
playerRef.current = newPlayer;
if (progress) {
newPlayer.getDuration().then((duration) => {
const seconds = duration * progress;
newPlayer.setCurrentTime(seconds);
});
}
const keepTrackProgress = async () => {
// gets duration of video in seconds
const duration = await newPlayer.getDuration();
intervalRef.current = window.setInterval(() => {
const currentPlayer = playerRef.current;
if (!currentPlayer) {
return;
}
currentPlayer.getCurrentTime().then((seconds) => {
// `seconds` indicates the current playback position of the video
const newProgress = seconds / duration;
console.tag(TAG).debug(`progress: ${newProgress}, duration ${duration}, seconds ${seconds}`);
onProgress(newProgress);
setProgress(newProgress);
});
// track every next 10 seconds of progress
}, 10000);
};
newPlayer.on('ended', () => {
console.tag(TAG).debug('player onEnded');
removeInterval(intervalRef.current);
intervalRef.current = null;
onProgress(1);
setProgress(1);
onVideoEnded();
});
newPlayer.on('pause', ({ duration, seconds }) => {
console.tag(TAG).debug('player onPause');
removeInterval(intervalRef.current);
intervalRef.current = null;
const newProgress = seconds / duration;
console.tag(TAG).debug(`progress at paused: ${newProgress}, duration ${duration}, seconds ${seconds}`);
onProgress(newProgress);
setProgress(newProgress);
});
newPlayer.on('play', () => {
console.tag(TAG).debug('player onPlay');
keepTrackProgress();
});
};
asyncEffect();
}, [delayedWidth]);
useEffect(() => () => {
removeInterval(intervalRef.current);
removeEventListeners(playerRef.current);
if (playerRef.current) {
playerRef.current.destroy();
}
}, []);
return (
<div id={`frame-${index}`} className="frame-wrapper" />
);
};
VideoPlayer.propTypes = {
index: PropTypes.number.isRequired,
link: PropTypes.string.isRequired,
onProgress: PropTypes.func.isRequired,
onVideoEnded: PropTypes.func,
latestProgress: PropTypes.number.isRequired,
widthPercentage: PropTypes.number,
};
VideoPlayer.defaultProps = {
widthPercentage: 0.9,
onVideoEnded: () => {},
};
export default VideoPlayer;
一定要仔细阅读这篇文章,以更好地了解细节-超级透彻,写得很好!
答案 3 :(得分:1)
您可以结合使用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.
});
}
答案 4 :(得分:0)
这是一个简单的完整解决方案,
每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);
}