| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116 |
- import { isFunction } from 'lodash-es';
- import React, { useCallback, useEffect, useRef, useState } from 'react';
- interface CountdownProps {
- duration: number;
- onTimeout?: (() => void) | null;
- loading: boolean;
- title?: string | React.ReactNode | ((countdown: number) => React.ReactNode);
- color?: string;
- }
- const Countdown: React.FC<CountdownProps> = React.memo(
- ({ duration, onTimeout, loading, color = '#faad14', title = '下次刷新:' }) => {
- const timerRef = useRef<NodeJS.Timeout | null>(null);
- const [countdown, setCountdown] = useState(duration);
- const onTimeoutRef = useRef(onTimeout);
- const loadingRef = useRef(loading);
- useEffect(() => {
- onTimeoutRef.current = onTimeout;
- }, [onTimeout]);
- useEffect(() => {
- loadingRef.current = loading;
- }, [loading]);
- // 启动定时器的函数
- const startTimer = useCallback(() => {
- timerRef.current = setInterval(() => {
- setCountdown((prev) => {
- const newCountdown = prev > 1 ? prev - 1 : duration;
- if (prev > 1) return newCountdown;
- setTimeout(() => {
- onTimeoutRef.current?.();
- }, 50);
- return newCountdown;
- });
- }, 1000);
- }, [duration]);
- useEffect(() => {
- // duration, loading, startTimer 任意一项产生变化时,都需要重置倒计时,并清除之前的定时器
- // 重置倒计时
- setCountdown(duration);
- // 清除之前的定时器
- if (timerRef.current) {
- clearInterval(timerRef.current);
- timerRef.current = null;
- }
- // 如果 loading 为 true,不启动定时器
- if (loading) return;
- startTimer();
- return () => {
- if (timerRef.current) {
- clearInterval(timerRef.current);
- timerRef.current = null;
- }
- };
- }, [duration, loading, startTimer]);
- // 监听页面可见性变化,当页面隐藏时暂停计时器,显示时恢复
- useEffect(() => {
- const handleVisibilityChange = () => {
- if (document.hidden) {
- // 页面隐藏:暂停计时器
- if (timerRef.current) {
- clearInterval(timerRef.current);
- timerRef.current = null;
- }
- } else {
- // 页面显示:恢复计时器
- if (!loadingRef.current && !timerRef.current) {
- // 只有在 loading 为 false 且定时器未运行时才恢复
- startTimer();
- }
- }
- };
- document.addEventListener('visibilitychange', handleVisibilityChange);
- return () => {
- document.removeEventListener('visibilitychange', handleVisibilityChange);
- };
- }, [startTimer]);
- return (
- <span style={{ color, marginRight: 16 }}>
- {isFunction(title) ? (
- title(countdown)
- ) : (
- <>
- {title}
- {countdown}s
- </>
- )}
- </span>
- );
- },
- (prevProps, nextProps) => {
- // 自定义比较函数:只比较影响 UI 和行为的 props,忽略 onTimeout 的变化(函数引用变化不影响 UI 渲染,通过 ref 存储,并在 useEffect 中更新,不重新渲染不会影响功能)
- return (
- prevProps.duration === nextProps.duration &&
- prevProps.loading === nextProps.loading &&
- prevProps.title === nextProps.title &&
- prevProps.color === nextProps.color
- );
- },
- );
- export default Countdown;
|