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>
This commit is contained in:
137
src/hooks/usePagination.ts
Normal file
137
src/hooks/usePagination.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user