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 = React.memo( ({ duration, onTimeout, loading, color = '#faad14', title = '下次刷新:' }) => { const timerRef = useRef(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 ( {isFunction(title) ? ( title(countdown) ) : ( <> {title} {countdown}s )} ); }, (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;