- 创建通用分页 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>
138 lines
3.3 KiB
TypeScript
138 lines
3.3 KiB
TypeScript
/**
|
||
* 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,
|
||
};
|
||
}
|