当不同的浏览器选项卡处于活动状态时 useEffect 中函数的行为
Behaviour of a function in useEffect when different browser tab is active
我有一个奇怪的问题。
我创建了一个旨在在 60 秒后重置 Linearprogress 元素的函数。
useEffect(() => {
const interval2 = setInterval(() => {
var internal = timer
if( internal < 100 ) {internal = (internal - (1.695 * -1 )) } else {internal = internal - 100}
setTimer(internal)
}, 1000)
return () => clearInterval(interval2)
}, [timer])
然后,我有一个像这样的线性进度元素的渲染:
return (
<div>
<LinearProgress
color ="secondary"
value={timer}
variant="determinate"
/>
</div>
);
现在奇怪的是:当我查看我的应用程序时,一切看起来都很正常,60 秒后,该条重新开始并重复。但是,当我在重置后更改浏览器中的活动选项卡并在 55 秒后返回时(栏应该接近尾端)- 栏在中间。
当应用程序的选项卡未处于活动状态时,useeffect 似乎没有像应有的那样频繁地重新执行该功能。
我在这里错过了什么。
CODE SANDBOX(问题在那里复制):https://codesandbox.io/s/young-brook-mttpz?file=/src/App.js:205-206
谢谢
由于 setInterval
,您有内存泄漏。
每 1000 毫秒,它会重新运行,但与此同时,您的 useEffect
也会被 setTimer(internal);
触发。所以你有越来越多的setInterval 运行.
一个解决方案是在更新 Timer
之前添加一个 clearInterval(interval2);
。
但从概念上讲它并不完美,因为我们使用间隔作为超时,所以您只需将 setInterval
替换为 setTimeout
并在 return clearInterval
clearTimeout
不修改任何其他内容。
这是经过修改后的代码的工作版本 sandbox:
import React from "react";
import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/styles";
import { LinearProgress } from "@material-ui/core";
import { useEffect } from "react";
const TotalProfit = (props) => {
const [timer, setTimer] = React.useState(0);
useEffect(() => {
const interval2 = setTimeout(() => {
var internal = timer;
if (internal < 100) {
internal = internal - 1.695 * -1;
} else {
internal = internal - 100;
}
setTimer(internal);
}, 1000);
return () => clearTimeout(interval2);
}, [timer]);
return (
<div>
<div>{timer}</div>
<LinearProgress color="secondary" value={timer} variant="determinate" />
</div>
);
};
TotalProfit.propTypes = {
className: PropTypes.string
};
export default TotalProfit;
如解释的那样 here, the browser allocates less ressources to not focused tabs, so timers can be wrong. So one of the solution given isto use a timer initialized when your components is first render. Then you use the difference between Date.now() and your first render time to have your interval (modulo 100) (sandbox)。
import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/styles";
import { LinearProgress } from "@material-ui/core";
const TotalProfit = (props) => {
const [timer] = useState(Date.now()/1000);
const [delta, setDelta] = useState(0);
useEffect(() => {
const interval2 = setInterval(() => {
setDelta((Date.now()/1000 - timer) % 100);
}, 1000);
return () => clearInterval(interval2);
}, [timer]);
return (
<div>
<div>{delta}</div>
<LinearProgress color="secondary" value={delta} variant="determinate" />
</div>
);
};
TotalProfit.propTypes = {
className: PropTypes.string
};
export default TotalProfit;
否则,如果您的目标只是拥有一个加载程序,您可以使用如图所示的 css 动画(稍微修改以获得与使用 js 获得的相同的渲染):
body {margin: 0; padding: 0;}
@keyframes loader-animation {
0% {
width: 0%;
}
100% {
width: 100%;
left: 0%
}
}
.loader {
height: 5px;
width: 100%;
}
.loader .bar {
position: absolute;
height: 5px;
background-color: dodgerblue;
animation-name: loader-animation;
animation-duration: 3s;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
}
<div class="loader">
<div class="bar"></div>
</div>
我有一个奇怪的问题。 我创建了一个旨在在 60 秒后重置 Linearprogress 元素的函数。
useEffect(() => {
const interval2 = setInterval(() => {
var internal = timer
if( internal < 100 ) {internal = (internal - (1.695 * -1 )) } else {internal = internal - 100}
setTimer(internal)
}, 1000)
return () => clearInterval(interval2)
}, [timer])
然后,我有一个像这样的线性进度元素的渲染:
return (
<div>
<LinearProgress
color ="secondary"
value={timer}
variant="determinate"
/>
</div>
);
现在奇怪的是:当我查看我的应用程序时,一切看起来都很正常,60 秒后,该条重新开始并重复。但是,当我在重置后更改浏览器中的活动选项卡并在 55 秒后返回时(栏应该接近尾端)- 栏在中间。
当应用程序的选项卡未处于活动状态时,useeffect 似乎没有像应有的那样频繁地重新执行该功能。
我在这里错过了什么。
CODE SANDBOX(问题在那里复制):https://codesandbox.io/s/young-brook-mttpz?file=/src/App.js:205-206
谢谢
由于 setInterval
,您有内存泄漏。
每 1000 毫秒,它会重新运行,但与此同时,您的 useEffect
也会被 setTimer(internal);
触发。所以你有越来越多的setInterval 运行.
一个解决方案是在更新 Timer
之前添加一个 clearInterval(interval2);
。
但从概念上讲它并不完美,因为我们使用间隔作为超时,所以您只需将 setInterval
替换为 setTimeout
并在 return clearInterval
clearTimeout
不修改任何其他内容。
这是经过修改后的代码的工作版本 sandbox:
import React from "react";
import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/styles";
import { LinearProgress } from "@material-ui/core";
import { useEffect } from "react";
const TotalProfit = (props) => {
const [timer, setTimer] = React.useState(0);
useEffect(() => {
const interval2 = setTimeout(() => {
var internal = timer;
if (internal < 100) {
internal = internal - 1.695 * -1;
} else {
internal = internal - 100;
}
setTimer(internal);
}, 1000);
return () => clearTimeout(interval2);
}, [timer]);
return (
<div>
<div>{timer}</div>
<LinearProgress color="secondary" value={timer} variant="determinate" />
</div>
);
};
TotalProfit.propTypes = {
className: PropTypes.string
};
export default TotalProfit;
如解释的那样 here, the browser allocates less ressources to not focused tabs, so timers can be wrong. So one of the solution given isto use a timer initialized when your components is first render. Then you use the difference between Date.now() and your first render time to have your interval (modulo 100) (sandbox)。
import React, { useEffect, useState } from "react";
import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/styles";
import { LinearProgress } from "@material-ui/core";
const TotalProfit = (props) => {
const [timer] = useState(Date.now()/1000);
const [delta, setDelta] = useState(0);
useEffect(() => {
const interval2 = setInterval(() => {
setDelta((Date.now()/1000 - timer) % 100);
}, 1000);
return () => clearInterval(interval2);
}, [timer]);
return (
<div>
<div>{delta}</div>
<LinearProgress color="secondary" value={delta} variant="determinate" />
</div>
);
};
TotalProfit.propTypes = {
className: PropTypes.string
};
export default TotalProfit;
否则,如果您的目标只是拥有一个加载程序,您可以使用如图所示的 css 动画(稍微修改以获得与使用 js 获得的相同的渲染)
body {margin: 0; padding: 0;}
@keyframes loader-animation {
0% {
width: 0%;
}
100% {
width: 100%;
left: 0%
}
}
.loader {
height: 5px;
width: 100%;
}
.loader .bar {
position: absolute;
height: 5px;
background-color: dodgerblue;
animation-name: loader-animation;
animation-duration: 3s;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
}
<div class="loader">
<div class="bar"></div>
</div>