import { useCallback, useEffect, useRef, useState } from 'react'; import { getApiCache } from './storage'; interface UseSWCOptions { onError?: (e: unknown) => void; onLoad?: (data: T) => void; cacheOnly?: boolean; cacheTimeout?: number; autoStart?: boolean; } const DEFAULT_CACHE_TTL = 86400 * 120; // 该 hook 在组件中按 key 和 action 拉取数据,自动处理加载/错误/缓存(stale-while-revalidate) export function useSWC(key: string, action: () => Promise, options?: UseSWCOptions) { const optionsRef = useRef(options || {}); optionsRef.current = options || {}; const keyRef = useRef(key); const [data, setData] = useState(() => getApiCache().getObject(keyRef.current) ); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const actionRef = useRef(action); actionRef.current = action; const dataRef = useRef(data); dataRef.current = data; const isMountedRef = useRef(true); const load = useCallback(async () => { setLoading(dataRef.current ? 'background' : true); setError(null); const opts = optionsRef.current; const cacheKey = keyRef.current; try { const result = await actionRef.current(); if (result) { getApiCache().setObject(cacheKey, result, opts.cacheTimeout || DEFAULT_CACHE_TTL); } if (isMountedRef.current) { opts.onLoad?.(result); setData(result); setLoading(false); } } catch (err) { if (isMountedRef.current) { opts.onError?.(err); setError(err); setLoading(false); } } }, []); useEffect(() => { isMountedRef.current = true; if (dataRef.current && optionsRef.current.cacheOnly) { setLoading(false); setError(null); } else if (optionsRef.current.autoStart !== false) { load(); } return () => { isMountedRef.current = false; }; }, [load]); const getOrLoad = useCallback(async () => { if (dataRef.current && optionsRef.current.cacheOnly) { setLoading(false); setError(null); return; } await load(); }, [load]); return { data, loading, error, load: getOrLoad, refresh: load }; }