| New file |
| | |
| | | import { useDidShow } from '@tarojs/taro'; |
| | | import { useCallback, useEffect, useRef } from 'react'; |
| | | |
| | | type UseLimitedPollingOptions = { |
| | | intervalMs: number; |
| | | maxPollCount: number; |
| | | canPoll: () => boolean; |
| | | onPoll: () => void; |
| | | onPollInvalid?: () => void; |
| | | onLimitReached?: () => void; |
| | | shouldResumeOnShow?: () => boolean; |
| | | onResumeBeforeStart?: () => void; |
| | | }; |
| | | |
| | | type UseLimitedPollingResult = { |
| | | ensurePolling: () => void; |
| | | restartPolling: () => void; |
| | | stopPolling: () => void; |
| | | disposePolling: () => void; |
| | | }; |
| | | |
| | | /** |
| | | * 通用受限轮询控制:负责次数上限、启停、重置与页面回前台恢复。 |
| | | */ |
| | | export default function useLimitedPolling({ |
| | | intervalMs, |
| | | maxPollCount, |
| | | canPoll, |
| | | onPoll, |
| | | onPollInvalid, |
| | | onLimitReached, |
| | | shouldResumeOnShow, |
| | | onResumeBeforeStart |
| | | }: UseLimitedPollingOptions): UseLimitedPollingResult { |
| | | const timerRef = useRef<ReturnType<typeof setInterval> | null>(null); // 当前轮询定时器 |
| | | const pollCountRef = useRef(0); // 当前轮询次数 |
| | | const stoppedByLimitRef = useRef(false); // 是否因超过轮询上限而停止 |
| | | const intervalMsRef = useRef(intervalMs); // 轮询间隔引用 |
| | | const maxPollCountRef = useRef(maxPollCount); // 最大轮询次数引用 |
| | | const canPollRef = useRef(canPoll); // 是否允许继续轮询 |
| | | const onPollRef = useRef(onPoll); // 单次轮询执行函数 |
| | | const onPollInvalidRef = useRef(onPollInvalid); // 轮询失效回调 |
| | | const onLimitReachedRef = useRef(onLimitReached); // 达到上限回调 |
| | | const shouldResumeOnShowRef = useRef(shouldResumeOnShow); // 页面展示时是否允许恢复 |
| | | const onResumeBeforeStartRef = useRef(onResumeBeforeStart); // 页面展示恢复前的预处理 |
| | | |
| | | intervalMsRef.current = intervalMs; |
| | | maxPollCountRef.current = maxPollCount; |
| | | canPollRef.current = canPoll; |
| | | onPollRef.current = onPoll; |
| | | onPollInvalidRef.current = onPollInvalid; |
| | | onLimitReachedRef.current = onLimitReached; |
| | | shouldResumeOnShowRef.current = shouldResumeOnShow; |
| | | onResumeBeforeStartRef.current = onResumeBeforeStart; |
| | | |
| | | /** |
| | | * 清理轮询定时器。 |
| | | */ |
| | | const clearPollTimer = useCallback(() => { |
| | | if (timerRef.current) { |
| | | clearInterval(timerRef.current); |
| | | timerRef.current = null; |
| | | } |
| | | }, []); |
| | | |
| | | /** |
| | | * 停止轮询但保留当前计数状态。 |
| | | */ |
| | | const stopPolling = useCallback(() => { |
| | | clearPollTimer(); |
| | | }, [clearPollTimer]); |
| | | |
| | | /** |
| | | * 重置轮询状态,用于重新开始一轮完整计数。 |
| | | */ |
| | | const resetPollingState = useCallback(() => { |
| | | pollCountRef.current = 0; |
| | | stoppedByLimitRef.current = false; |
| | | }, []); |
| | | |
| | | /** |
| | | * 确保轮询已启动;已存在定时器时不重复创建。 |
| | | */ |
| | | const ensurePolling = useCallback(() => { |
| | | if (timerRef.current || stoppedByLimitRef.current) { |
| | | return; |
| | | } |
| | | timerRef.current = setInterval(() => { |
| | | if (!canPollRef.current()) { |
| | | stopPolling(); |
| | | onPollInvalidRef.current?.(); |
| | | return; |
| | | } |
| | | if (pollCountRef.current >= maxPollCountRef.current) { |
| | | stopPolling(); |
| | | stoppedByLimitRef.current = true; |
| | | onLimitReachedRef.current?.(); |
| | | return; |
| | | } |
| | | pollCountRef.current += 1; |
| | | onPollRef.current(); |
| | | }, intervalMsRef.current); |
| | | }, [stopPolling]); |
| | | |
| | | /** |
| | | * 重新开始一轮轮询,并清空上一次累计次数。 |
| | | */ |
| | | const restartPolling = useCallback(() => { |
| | | stopPolling(); |
| | | resetPollingState(); |
| | | ensurePolling(); |
| | | }, [ensurePolling, resetPollingState, stopPolling]); |
| | | |
| | | /** |
| | | * 彻底销毁轮询状态,供页面离开或功能停用时调用。 |
| | | */ |
| | | const disposePolling = useCallback(() => { |
| | | stopPolling(); |
| | | resetPollingState(); |
| | | }, [resetPollingState, stopPolling]); |
| | | |
| | | useDidShow(() => { |
| | | if (!stoppedByLimitRef.current) { |
| | | return; |
| | | } |
| | | if (!shouldResumeOnShowRef.current?.()) { |
| | | return; |
| | | } |
| | | onResumeBeforeStartRef.current?.(); |
| | | restartPolling(); |
| | | }); |
| | | |
| | | useEffect(() => { |
| | | return () => { |
| | | clearPollTimer(); |
| | | }; |
| | | }, [clearPollTimer]); |
| | | |
| | | return { |
| | | ensurePolling, |
| | | restartPolling, |
| | | stopPolling, |
| | | disposePolling |
| | | }; |
| | | } |