| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879 |
- import { useCallback, useEffect, useRef, useState } from 'react';
- import { getApiCache } from './storage';
- interface UseSWCOptions<T> {
- 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<T>(key: string, action: () => Promise<T>, options?: UseSWCOptions<T>) {
- const optionsRef = useRef(options || {});
- optionsRef.current = options || {};
- const keyRef = useRef(key);
- const [data, setData] = useState<T | null | undefined>(() =>
- getApiCache().getObject<T>(keyRef.current)
- );
- const [loading, setLoading] = useState<true | 'background' | false>(true);
- const [error, setError] = useState<unknown>(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 };
- }
|