使用react hooks写一个倒计时功能

发布于:2024-05-07 ⋅ 阅读:(24) ⋅ 点赞:(0)

先要明确的是,setTimeout函数中执行代码的时间肯定是要大于等于setTimeout时间的,那么就可能出现设定的 1 秒,实际执行却执行了 2 秒的情况,那么我们的实现思路也很简单,每次计算一下setTimeout实际执行的时间,然后动态的调整下一次执行的时间,而不是设置固定的值

第n次执行 executionTime 实际执行时间 nextTime 下次需要执行的时间 totleTime 执行的总时间
0 0 1000 0
1 1200 800 1200
2 1100 700 2300
3 1000 700 3300
4 2200 500 5500
5 1300 200 6800
6 1200 1000 8000

const useCountDown = ({ leftTime, ms = 1000, onEnd }) => {
    const countdownTimer = useRef();
    const startTimer = useRef();
    //记录初始时间
    const startTimeRef = useRef(performance.now());
    // 第一次执行的时间处理,让下一次倒计时时调整为整数
    const nextTimeRef = useRef(leftTime % ms);

    const [count, setCount] = useState(leftTime);

    const clearTimer = () => {
        countdownTimer.current && clearTimeout(countdownTimer.current);
        startTimer.current && clearTimeout(startTimer.current);
    };

    const startCountDown = () => {
        clearTimer();
        const currentTime = performance.now();
        // 算出每次实际执行的时间
        const executionTime = currentTime - startTimeRef.current;

        // 实际执行时间大于上一次需要执行的时间,说明执行时间多了,否则需要补上差的时间
        const diffTime =
            executionTime > nextTimeRef.current
                ? executionTime - nextTimeRef.current
                : nextTimeRef.current - executionTime;

        setCount((count) => {
            const nextCount =
                count - (Math.floor(executionTime / ms) || 1) * ms - nt;
            return nextCount <= 0 ? 0 : nextCount;
        });

        // 算出下一次的时间
        nextTimeRef.current =
            executionTime > nextTimeRef.current ? ms - diffTime : ms + diffTime;

        // 重置初始时间
        startTimeRef.current = performance.now();

        countdownTimer.current = setTimeout(() => {
            requestAnimationFrame(startCountDown);
        }, nextTimeRef.current);
    };

    useEffect(() => {
        setCount(leftTime);
        startTimer.current = setTimeout(startCountDown, nextTimeRef.current);
        return () => {
  clearTimer();
        };
    }, [leftTime]);

    useEffect(() => {
        if (count <= 0) {
            clearTimer();
            onEnd && onEnd();
        }
    }, [count]);

    return count;
};

export default useCountDown;