UseEffect 中的 React setInterval 未在后台选项卡中正确更新
React setInterval in UseEffect not updating correctly in background tab
我正在尝试编写一个 (pomodoro) 计时器,我将 运行 保留在这个问题中,如果代码在后台选项卡中 运行 一段时间,大约 5- 10 分钟以上(至少在 chrome 上),它滞后并最终花费比完成计时器更长的时间。我已将间隔更改为 1000 毫秒,并尝试使用 refs 而不是 state。
代码可在 github
演示在 gh-pages
import { useState, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { ReactDOM } from 'react-dom';
const Timer = ({ time, addTomato }) => {
const [ timeRemaining, setTimeRemaining ] = useState(time * 60);
const [ isPaused, setIsPaused ] = useState(true);
const timeRemainingRef = useRef(timeRemaining);
const isPausedRef = useRef(isPaused);
let navigate = useNavigate();
let minutes = Math.floor(timeRemaining / 60).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false
});
let seconds = Math.floor(timeRemaining % 60).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false
});
function timer() {
if (isPausedRef.current) return;
if (timeRemainingRef.current === 0) {
addTomato();
navigate("/trackodoro/break/");
return
}
timeRemainingRef.current --;
document.title = `Trackodoro ${Math.floor(timeRemainingRef.current / 60).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false
})}:${Math.floor(timeRemainingRef.current % 60).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false
})}`
setTimeRemaining(timeRemainingRef.current)
}
useEffect( //maybe i dont wanty to use useeffect
() => {
const myTimer = setInterval(timer, 1000);
return () => clearInterval(myTimer); //when I pause, it doesnt count down to next number
},
[]
);
const pauseTimer = () => {
isPausedRef.current = !isPausedRef.current
setIsPaused(isPausedRef.current);
};
const resetTimer = () => {
!isPausedRef.current && pauseTimer();
timeRemainingRef.current = time * 60
setTimeRemaining(timeRemainingRef.current);
}
return (
<div>
<h1>
{minutes}:{seconds}
</h1>
<button className="btn btn-block" onClick={pauseTimer}>
{isPaused && timeRemaining === time * 60 ? 'Start' : isPaused ? 'Resume' : 'Pause'}
</button>
<button className="btn btn-block" onClick={resetTimer}>
Reset
</button>
</div>
);
};
export default Timer;
而不是 timeRemainingRef.current --
,您可能应该使用结束日期时间。
带有 date-fns.
的示例(伪代码)
const expiredate = addMinutes(new Date(), 5) //expires in 5 minutes.
在你需要的地方timeRemainingRef.current --
timeRemainingRef.current = differenceInSeconds(expiredate, new Date())
timeRemainingRef.current
将是 expiredate
剩余的秒数
使用日期时间差异可能比使用 +1 计数器更可靠。
addMinutes 参考 - https://date-fns.org/v2.28.0/docs/addMinutes
秒差参考 - https://date-fns.org/v2.28.0/docs/differenceInSeconds
date-fns 是一个轻量级的日期时间实用程序。
我正在尝试编写一个 (pomodoro) 计时器,我将 运行 保留在这个问题中,如果代码在后台选项卡中 运行 一段时间,大约 5- 10 分钟以上(至少在 chrome 上),它滞后并最终花费比完成计时器更长的时间。我已将间隔更改为 1000 毫秒,并尝试使用 refs 而不是 state。
代码可在 github
演示在 gh-pages
import { useState, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { ReactDOM } from 'react-dom';
const Timer = ({ time, addTomato }) => {
const [ timeRemaining, setTimeRemaining ] = useState(time * 60);
const [ isPaused, setIsPaused ] = useState(true);
const timeRemainingRef = useRef(timeRemaining);
const isPausedRef = useRef(isPaused);
let navigate = useNavigate();
let minutes = Math.floor(timeRemaining / 60).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false
});
let seconds = Math.floor(timeRemaining % 60).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false
});
function timer() {
if (isPausedRef.current) return;
if (timeRemainingRef.current === 0) {
addTomato();
navigate("/trackodoro/break/");
return
}
timeRemainingRef.current --;
document.title = `Trackodoro ${Math.floor(timeRemainingRef.current / 60).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false
})}:${Math.floor(timeRemainingRef.current % 60).toLocaleString('en-US', {
minimumIntegerDigits: 2,
useGrouping: false
})}`
setTimeRemaining(timeRemainingRef.current)
}
useEffect( //maybe i dont wanty to use useeffect
() => {
const myTimer = setInterval(timer, 1000);
return () => clearInterval(myTimer); //when I pause, it doesnt count down to next number
},
[]
);
const pauseTimer = () => {
isPausedRef.current = !isPausedRef.current
setIsPaused(isPausedRef.current);
};
const resetTimer = () => {
!isPausedRef.current && pauseTimer();
timeRemainingRef.current = time * 60
setTimeRemaining(timeRemainingRef.current);
}
return (
<div>
<h1>
{minutes}:{seconds}
</h1>
<button className="btn btn-block" onClick={pauseTimer}>
{isPaused && timeRemaining === time * 60 ? 'Start' : isPaused ? 'Resume' : 'Pause'}
</button>
<button className="btn btn-block" onClick={resetTimer}>
Reset
</button>
</div>
);
};
export default Timer;
而不是 timeRemainingRef.current --
,您可能应该使用结束日期时间。
带有 date-fns.
的示例(伪代码)const expiredate = addMinutes(new Date(), 5) //expires in 5 minutes.
在你需要的地方timeRemainingRef.current --
timeRemainingRef.current = differenceInSeconds(expiredate, new Date())
timeRemainingRef.current
将是 expiredate
使用日期时间差异可能比使用 +1 计数器更可靠。
addMinutes 参考 - https://date-fns.org/v2.28.0/docs/addMinutes
秒差参考 - https://date-fns.org/v2.28.0/docs/differenceInSeconds
date-fns 是一个轻量级的日期时间实用程序。