import { useState, useEffect, useCallback, useRef } from 'react' export interface CountDownResult { /** 剩余秒数 */ seconds: number /** 剩余分钟 */ minutes: number /** 剩余小时 */ hours: number /** 剩余天数 */ days: number /** 总剩余秒数 */ totalSeconds: number /** 是否在运行 */ isRunning: boolean /** 是否已结束 */ isExpired: boolean /** 格式化后的字符串 */ formatted: string /** 暂停 */ pause: () => void /** 继续 */ resume: () => void /** 重置 */ reset: (newTargetDate?: number) => void } /** * 倒计时 Hook * @param targetDate 目标时间戳(毫秒),0 表示立即结束 * @param options 配置选项 */ export function useCountDown( targetDate: number, options: { /** 触发间隔(毫秒),默认 1000 */ interval?: number /** 结束时的回调 */ onComplete?: () => void /** 是否自动开始,默认 true */ autoStart?: boolean /** 格式化函数,默认返回 HH:MM:SS 或 DD天HH:MM:SS */ format?: (result: Pick) => string } = {} ): CountDownResult { const { interval = 1000, onComplete, autoStart = true, format } = options const targetRef = useRef(targetDate) const onCompleteRef = useRef(onComplete) const intervalRef = useRef | null>(null) const [state, setState] = useState(() => calculateTimeLeft(targetDate)) const [isRunning, setIsRunning] = useState(autoStart) // 更新回调引用 useEffect(() => { onCompleteRef.current = onComplete }, [onComplete]) // 更新目标时间 useEffect(() => { targetRef.current = targetDate setState(calculateTimeLeft(targetDate)) }, [targetDate]) // 计算剩余时间 function calculateTimeLeft(target: number) { const now = Date.now() const diff = Math.max(0, Math.floor((target - now) / 1000)) return { totalSeconds: diff, days: Math.floor(diff / 86400), hours: Math.floor((diff % 86400) / 3600), minutes: Math.floor((diff % 3600) / 60), seconds: diff % 60, } } // 默认格式化 const defaultFormat = useCallback((r: Pick) => { const pad = (n: number) => String(n).padStart(2, '0') if (r.days > 0) { return `${r.days}天${pad(r.hours)}:${pad(r.minutes)}:${pad(r.seconds)}` } return `${pad(r.hours)}:${pad(r.minutes)}:${pad(r.seconds)}` }, []) // 启动定时器 const startInterval = useCallback(() => { if (intervalRef.current) clearInterval(intervalRef.current) intervalRef.current = setInterval(() => { const left = calculateTimeLeft(targetRef.current) setState(left) if (left.totalSeconds <= 0) { if (intervalRef.current) clearInterval(intervalRef.current) setIsRunning(false) onCompleteRef.current?.() } }, interval) }, [interval]) // 控制定时器 useEffect(() => { if (isRunning) { // 立即执行一次 const left = calculateTimeLeft(targetRef.current) setState(left) if (left.totalSeconds <= 0) { setIsRunning(false) onCompleteRef.current?.() } else { startInterval() } } else { if (intervalRef.current) { clearInterval(intervalRef.current) intervalRef.current = null } } return () => { if (intervalRef.current) clearInterval(intervalRef.current) } }, [isRunning, startInterval]) const pause = useCallback(() => setIsRunning(false), []) const resume = useCallback(() => { if (state.totalSeconds > 0) setIsRunning(true) }, [state.totalSeconds]) const reset = useCallback((newTarget?: number) => { const target = newTarget ?? targetDate targetRef.current = target setState(calculateTimeLeft(target)) setIsRunning(true) }, [targetDate]) return { ...state, isRunning, isExpired: state.totalSeconds <= 0, formatted: (format ?? defaultFormat)(state), pause, resume, reset, } }