Files
vf_react/src/hooks/usePagination.ts
zdl 9fd618c087 feat: 实现评论分页功能并迁移到 TypeScript
- 创建通用分页 Hook (usePagination.ts) 支持任意数据类型
- 将 EventCommentSection 迁移到 TypeScript (.tsx)
- 添加"加载更多"按钮,支持增量加载评论
- 创建分页和评论相关类型定义 (pagination.ts, comment.ts)
- 增加 Mock 评论数据从 5 条到 15 条,便于测试分页

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 17:27:12 +08:00

138 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* usePagination - 通用分页 Hook
*
* 封装分页逻辑,支持初始加载、加载更多、重置等功能
*
* @example
* const {
* data: comments,
* loading,
* loadingMore,
* hasMore,
* totalCount,
* loadMore,
* setData,
* setTotalCount,
* } = usePagination<Comment>(loadCommentsFunction, { pageSize: 5 });
*/
import { useState, useCallback, useEffect } from 'react';
import type {
LoadFunction,
PaginationLoadResult,
UsePaginationOptions,
UsePaginationResult,
} from '@/types/pagination';
/**
* usePagination Hook
* @template T 数据项类型
* @param loadFunction 加载函数,接收 (page, append) 参数
* @param options 配置选项
* @returns 分页状态和操作方法
*/
export function usePagination<T>(
loadFunction: LoadFunction<T>,
options: UsePaginationOptions = {}
): UsePaginationResult<T> {
const { pageSize = 10, autoLoad = true } = options;
// 状态管理
const [data, setData] = useState<T[]>([]);
const [loading, setLoading] = useState(false);
const [loadingMore, setLoadingMore] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [totalCount, setTotalCount] = useState(0);
const [hasMore, setHasMore] = useState(false);
/**
* 加载数据
* @param page 页码
* @param append 是否追加true: 追加false: 替换)
*/
const loadData = useCallback(
async (page: number, append: boolean = false) => {
// 设置加载状态
if (append) {
setLoadingMore(true);
} else {
setLoading(true);
}
try {
const result: PaginationLoadResult<T> = await loadFunction(page, append);
// 更新数据
if (append) {
setData((prevData) => [...prevData, ...(result.data || [])]);
} else {
setData(result.data || []);
}
// 更新分页信息
const total = result.pagination?.total || result.data?.length || 0;
setTotalCount(total);
// 计算是否还有更多数据
const currentTotal = append
? data.length + (result.data?.length || 0)
: result.data?.length || 0;
setHasMore(currentTotal < total);
} catch (error) {
console.error('[usePagination] 加载数据失败:', error);
throw error;
} finally {
if (append) {
setLoadingMore(false);
} else {
setLoading(false);
}
}
},
[loadFunction, data.length]
);
/**
* 加载更多数据
*/
const loadMore = useCallback(async () => {
if (loadingMore || !hasMore) return;
const nextPage = currentPage + 1;
await loadData(nextPage, true);
setCurrentPage(nextPage);
}, [currentPage, loadData, loadingMore, hasMore]);
/**
* 重置到第一页
*/
const reset = useCallback(() => {
setCurrentPage(1);
setData([]);
setTotalCount(0);
setHasMore(false);
loadData(1, false);
}, [loadData]);
// 自动加载第一页
useEffect(() => {
if (autoLoad) {
loadData(1, false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [autoLoad]);
return {
data,
loading,
loadingMore,
currentPage,
hasMore,
totalCount,
loadMore,
reset,
setData,
setTotalCount,
};
}