diff --git a/hooks/data/use-template-generations.ts b/hooks/data/use-template-generations.ts index c903647..9f1d797 100644 --- a/hooks/data/use-template-generations.ts +++ b/hooks/data/use-template-generations.ts @@ -8,85 +8,108 @@ import { handleError } from './use-error' const pageSize = 12 +// 公共工具函数 +const getController = () => root.get(TemplateGenerationController) + +const filterValidItems = (items: unknown[]): TemplateGeneration[] => + (items || []).filter((item: unknown): item is TemplateGeneration => { + return Boolean( + item && typeof item === 'object' && item !== null && 'id' in item && (item as Record).id, + ) + }) + +const calculateHasMore = (items: unknown[], limit: number): boolean => Array.isArray(items) && items.length === limit + export const useTemplateGenerations = () => { const [loading, setLoading] = useState(true) const [loadingMore, setLoadingMore] = useState(false) const [error, setError] = useState(null) const [data, setData] = useState([]) + const [hasMore, setHasMore] = useState(true) const currentPageRef = useRef(1) - const hasMoreRef = useRef(true) + const abortControllerRef = useRef(null) const load = useCallback(async (params?: ListTemplateGenerationsInput) => { + // 取消之前的请求 + if (abortControllerRef.current) { + abortControllerRef.current.abort() + } + abortControllerRef.current = new AbortController() + setLoading(true) setError(null) currentPageRef.current = 1 - const templateGeneration = root.get(TemplateGenerationController) - const { data, error } = await handleError( - async () => - await templateGeneration.list({ - page: 1, - limit: params?.limit || pageSize, - ...params, - }), + const limit = params?.limit || pageSize + const { data: responseData, error } = await handleError(async () => + getController().list({ + page: 1, + limit, + ...params, + }), ) + // 如果请求被中止,直接返回 + if (abortControllerRef.current?.signal.aborted) { + return + } + if (error) { setError(error) setLoading(false) - return + return { data: undefined, error } } - const items = data?.data || [] - - hasMoreRef.current = items.length >= (params?.limit || pageSize) - - const filterData = items?.filter((item) => !!item.id) + const items = responseData?.data || [] + const filterData = filterValidItems(items) + const hasMoreResult = calculateHasMore(items, limit) setData(filterData) + setHasMore(hasMoreResult) setLoading(false) - return { data, error: null } + abortControllerRef.current = null + return { data: filterData, error: null } }, []) const loadMore = useCallback( async (params?: Omit) => { - const hasMore = hasMoreRef.current - if (!hasMore) return + // 防止竞态条件和无效请求 + if (loading || loadingMore || !hasMore) return setLoadingMore(true) const nextPage = currentPageRef.current + 1 + const limit = params?.limit || pageSize - const templateGeneration = root.get(TemplateGenerationController) - - const { data: newData, error } = await handleError( - async () => - await templateGeneration.list({ - page: nextPage, - limit: params?.limit || pageSize, - ...params, - }), + const { data: responseData, error } = await handleError(async () => + getController().list({ + page: nextPage, + limit, + ...params, + }), ) if (error) { setLoadingMore(false) + setError(error) return { data: undefined, error } } - const newItems = newData?.data || [] - hasMoreRef.current = newItems.length >= (params?.limit || pageSize) + const newItems = responseData?.data || [] + const filterData = filterValidItems(newItems) + const hasMoreResult = calculateHasMore(newItems, limit) currentPageRef.current = nextPage - const filterData = newItems?.filter((item) => !!item.id) setData((prev) => [...prev, ...filterData]) + setHasMore(hasMoreResult) setLoadingMore(false) - return { data: newData, error: null } + return { data: filterData, error: null } }, - [loading, loadingMore], + [loading, loadingMore, hasMore], ) const refetch = useCallback( (params?: ListTemplateGenerationsInput) => { - hasMoreRef.current = true + setHasMore(true) return load(params) }, [load], @@ -94,7 +117,15 @@ export const useTemplateGenerations = () => { useEffect(() => { load() - }, []) + + // 清理函数:组件卸载时取消未完成的请求 + return () => { + if (abortControllerRef.current) { + abortControllerRef.current.abort() + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) // 只在组件挂载时执行一次初始加载 return { data, @@ -104,6 +135,6 @@ export const useTemplateGenerations = () => { load, refetch, loadMore, - hasMore: hasMoreRef.current, + hasMore, } }