问题描述:
- 在事件 A 下发表评论后,该评论会出现在事件 B、C 等所有事件下
- 切换事件时,评论列表没有重新加载,导致数据混乱
根本原因:
- usePagination Hook 的 useEffect 只依赖 autoLoad(常量)
- 当 eventId 变化时,loadCommentsFunction 被重新创建(包含新的 eventId)
- 但 useEffect 不会重新执行,导致旧数据(上一个事件的评论)持续显示
修复方案:
- 在 useEffect 依赖数组中添加 loadFunction
- 当 loadFunction 变化时(eventId 变化 → loadCommentsFunction 变化)
- useEffect 重新执行,加载新事件的评论数据
影响范围:
- EventCommentSection 组件(评论区)
- 所有使用 usePagination Hook 的组件都会受益于此修复
- 确保数据隔离性和正确性
🤖 Generated with Claude Code
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, loadFunction]);
|
||
|
||
return {
|
||
data,
|
||
loading,
|
||
loadingMore,
|
||
currentPage,
|
||
hasMore,
|
||
totalCount,
|
||
loadMore,
|
||
reset,
|
||
setData,
|
||
setTotalCount,
|
||
};
|
||
}
|