| | |
| | | export default function useMinimumSpinLoading<T extends string>() { |
| | | /** 当前处于 loading 展示中的控件标识。 */ |
| | | const [visibleLoadingKey, setVisibleLoadingKey] = useState<T | null>(null); |
| | | /** visibleLoadingKey 的同步镜像,供 stopLoading 内即时读取,避免闭包拿到旧值。 */ |
| | | const visibleLoadingKeyRef = useRef<T | null>(null); |
| | | /** 当前 loading 开始时间戳。 */ |
| | | const loadingStartedAtRef = useRef(0); |
| | | /** 延迟关闭 loading 的定时器。 */ |
| | | const loadingFinishTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null); |
| | | |
| | | /** |
| | | * 同步更新 state 与 ref,保证两者一致。 |
| | | */ |
| | | const setKey = useCallback((key: T | null) => { |
| | | visibleLoadingKeyRef.current = key; |
| | | setVisibleLoadingKey(key); |
| | | }, []); |
| | | |
| | | /** |
| | | * 清理延迟关闭定时器。 |
| | |
| | | const startLoading = useCallback((key: T) => { |
| | | clearLoadingFinishTimer(); |
| | | loadingStartedAtRef.current = Date.now(); |
| | | setVisibleLoadingKey(key); |
| | | }, [clearLoadingFinishTimer]); |
| | | setKey(key); |
| | | }, [clearLoadingFinishTimer, setKey]); |
| | | |
| | | /** |
| | | * 结束 loading 动画;若未满一圈则延迟到一圈完成后再关闭。 |
| | | * @param onStopped loading 真正关闭(含最短时长补足)后触发,用于把 toast 等提示对齐到动画结束,避免 loading 仍在转就弹出提示。 |
| | | */ |
| | | const stopLoading = useCallback((key?: T | null) => { |
| | | setVisibleLoadingKey((currentKey) => { |
| | | if (!currentKey) { |
| | | return currentKey; |
| | | } |
| | | if (key && currentKey !== key) { |
| | | return currentKey; |
| | | } |
| | | const elapsed = Date.now() - loadingStartedAtRef.current; |
| | | const remaining = Math.max(0, MINIMUM_SPIN_DURATION_MS - elapsed); |
| | | clearLoadingFinishTimer(); |
| | | if (remaining === 0) { |
| | | return null; |
| | | } |
| | | loadingFinishTimerRef.current = setTimeout(() => { |
| | | setVisibleLoadingKey((latestKey) => (latestKey === currentKey ? null : latestKey)); |
| | | loadingFinishTimerRef.current = null; |
| | | }, remaining); |
| | | return currentKey; |
| | | }); |
| | | }, [clearLoadingFinishTimer]); |
| | | const stopLoading = useCallback((key?: T | null, onStopped?: () => void) => { |
| | | const currentKey = visibleLoadingKeyRef.current; |
| | | // 无 loading 或指定 key 不匹配:不关闭,但仍执行回调作为兜底,保证提示不丢失。 |
| | | if (!currentKey || (key && currentKey !== key)) { |
| | | onStopped?.(); |
| | | return; |
| | | } |
| | | const elapsed = Date.now() - loadingStartedAtRef.current; |
| | | const remaining = Math.max(0, MINIMUM_SPIN_DURATION_MS - elapsed); |
| | | clearLoadingFinishTimer(); |
| | | if (remaining === 0) { |
| | | setKey(null); |
| | | onStopped?.(); |
| | | return; |
| | | } |
| | | loadingFinishTimerRef.current = setTimeout(() => { |
| | | setKey(null); |
| | | loadingFinishTimerRef.current = null; |
| | | onStopped?.(); |
| | | }, remaining); |
| | | }, [clearLoadingFinishTimer, setKey]); |
| | | |
| | | return { |
| | | visibleLoadingKey, |