import { useCallback, useEffect, useRef, useState } from 'react';
|
|
/** loading 动画至少完成一圈的展示时长(毫秒)。 */
|
const MINIMUM_SPIN_DURATION_MS = 1000;
|
|
/**
|
* 控制 loading 动画最短展示时长,避免图标未完成一圈旋转就切回普通态。
|
*/
|
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 clearLoadingFinishTimer = useCallback(() => {
|
if (loadingFinishTimerRef.current) {
|
clearTimeout(loadingFinishTimerRef.current);
|
loadingFinishTimerRef.current = null;
|
}
|
}, []);
|
|
useEffect(() => {
|
return () => {
|
clearLoadingFinishTimer();
|
};
|
}, [clearLoadingFinishTimer]);
|
|
/**
|
* 开始展示 loading 动画。
|
*/
|
const startLoading = useCallback((key: T) => {
|
clearLoadingFinishTimer();
|
loadingStartedAtRef.current = Date.now();
|
setKey(key);
|
}, [clearLoadingFinishTimer, setKey]);
|
|
/**
|
* 结束 loading 动画;若未满一圈则延迟到一圈完成后再关闭。
|
* @param onStopped loading 真正关闭(含最短时长补足)后触发,用于把 toast 等提示对齐到动画结束,避免 loading 仍在转就弹出提示。
|
*/
|
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,
|
startLoading,
|
stopLoading
|
};
|
}
|