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 是一个轻量级的日期时间实用程序。