refactor: 删除未使用的 lastUpdated 和 cachedCount 状态

- 删除 initialState 中的 lastUpdated 和 cachedCount
  - 删除所有 reducer 中相关的设置代码
  - 更新 selectors 使用 .length 替代 cachedCount
  - 删除 shouldRefresh 工具函数

  简化理由:
  - lastUpdated 未被使用
  - cachedCount 可以通过 events.length 直接获取
This commit is contained in:
zdl
2025-11-05 22:33:25 +08:00
parent ed24a14fbf
commit 6930878ff6
3 changed files with 155 additions and 114 deletions

View File

@@ -35,7 +35,7 @@ const VirtualizedFourRowGrid = ({
getTimelineBoxStyle,
borderColor,
loadNextPage,
loadPrevPage, // 新增:加载上一页
onRefreshFirstPage, // 修改:顶部刷新回调(替代 loadPrevPage
hasMore,
loading,
error, // 新增:错误状态
@@ -43,7 +43,7 @@ const VirtualizedFourRowGrid = ({
}) => {
const parentRef = useRef(null);
const isLoadingMore = useRef(false); // 防止重复加载
const previousScrollHeight = useRef(0); // 记录加载前的滚动高度(用于位置保持
const lastRefreshTime = useRef(0); // 记录上次刷新时间用于30秒防抖
// 滚动条颜色(主题适配)
const scrollbarTrackBg = useColorModeValue('#f1f1f1', '#2D3748');
@@ -67,7 +67,30 @@ const VirtualizedFourRowGrid = ({
overscan: 2, // 预加载2行上下各1行
});
// 双向无限滚动逻辑 - 监听滚动事件,到达底部加载下一页,到达顶部加载上一页
/**
* 【核心逻辑1】无限滚动 + 顶部刷新 - 监听滚动事件,根据滚动位置自动加载数据或刷新
*
* 工作原理:
* 1. 向下滚动到 60% 位置时,触发 loadNextPage()
* - 调用 usePagination.loadNextPage()
* - 内部执行 handlePageChange(currentPage + 1)
* - dispatch(fetchDynamicNews({ page: nextPage }))
* - 后端返回下一页数据30条
* - Redux 去重后追加到 fourRowEvents 数组
* - events prop 更新,虚拟滚动自动渲染新内容
*
* 2. 向上滚动到顶部 10% 以内时,触发 onRefreshFirstPage()
* - 清空缓存 + 重新加载第一页(获取最新数据)
* - 30秒防抖避免频繁刷新
* - 与5分钟定时刷新协同工作
*
* 设计要点:
* - 60% 触发点:提前加载,避免滚动到底部时才出现加载状态
* - 防抖机制isLoadingMore.current 防止重复触发
* - 两层缓存:
* - Redux 缓存HTTP层fourRowEvents 数组存储已加载数据,避免重复请求
* - 虚拟滚动缓存(渲染层):@tanstack/react-virtual 只渲染可见行,复用 DOM 节点
*/
useEffect(() => {
const scrollElement = parentRef.current;
if (!scrollElement) return;
@@ -81,30 +104,56 @@ const VirtualizedFourRowGrid = ({
// 向下滚动:滚动到 60% 时开始加载下一页
if (loadNextPage && hasMore && scrollPercentage > 0.6) {
console.log('%c📜 [双向滚动] 到达底部,加载下一页', 'color: #8B5CF6; font-weight: bold;');
console.log('%c📜 [无限滚动] 到达底部,加载下一页', 'color: #8B5CF6; font-weight: bold;');
isLoadingMore.current = true;
await loadNextPage();
isLoadingMore.current = false;
}
// 向上滚动:滚动到顶部 10% 以内时加载上一页
if (loadPrevPage && scrollTop < clientHeight * 0.1) {
console.log('%c📜 [双向滚动] 到达顶部,加载上一页', 'color: #10B981; font-weight: bold;');
isLoadingMore.current = true;
// 向上滚动到顶部触发刷新30秒防抖
if (onRefreshFirstPage && scrollTop < clientHeight * 0.1) {
const now = Date.now();
const timeSinceLastRefresh = now - lastRefreshTime.current;
// 记录加载前的滚动高度(用于位置保持)
previousScrollHeight.current = scrollHeight;
// 30秒防抖避免频繁刷新
if (timeSinceLastRefresh >= 30000) {
console.log('%c🔄 [顶部刷新] 滚动到顶部,清空缓存并重新加载第一页', 'color: #10B981; font-weight: bold;', {
timeSinceLastRefresh: `${(timeSinceLastRefresh / 1000).toFixed(1)}`
});
isLoadingMore.current = true;
lastRefreshTime.current = now;
await loadPrevPage();
isLoadingMore.current = false;
await onRefreshFirstPage();
isLoadingMore.current = false;
} else {
const remainingTime = Math.ceil((30000 - timeSinceLastRefresh) / 1000);
console.log('%c🔄 [顶部刷新] 防抖中,请等待', 'color: #EAB308; font-weight: bold;', {
remainingTime: `${remainingTime}`
});
}
}
};
scrollElement.addEventListener('scroll', handleScroll);
return () => scrollElement.removeEventListener('scroll', handleScroll);
}, [loadNextPage, loadPrevPage, hasMore, loading]);
}, [loadNextPage, onRefreshFirstPage, hasMore, loading]);
// 主动检测内容高度 - 如果内容不足以填满容器,主动加载下一页
/**
* 【核心逻辑2】主动检测内容高度 - 确保内容始终填满容器
*
* 场景:
* - 初次加载时,如果 30 条数据不足以填满 800px 容器(例如显示器很大)
* - 用户无法滚动,也就无法触发上面的滚动监听逻辑
*
* 解决方案:
* - 定时检查 scrollHeight 是否小于等于 clientHeight
* - 如果内容不足,主动调用 loadNextPage() 加载更多数据
* - 递归触发,直到内容高度超过容器高度(出现滚动条)
*
* 优化:
* - 500ms 延迟:确保虚拟滚动已完成首次渲染和高度测量
* - 监听 events.length 变化:新数据加载后重新检查
*/
useEffect(() => {
const scrollElement = parentRef.current;
if (!scrollElement || !loadNextPage) return;
@@ -133,36 +182,6 @@ const VirtualizedFourRowGrid = ({
return () => clearTimeout(timer);
}, [events.length, hasMore, loading, loadNextPage]);
// 滚动位置保持 - 加载上一页后,调整 scrollTop 使用户看到的内容位置不变
useEffect(() => {
const scrollElement = parentRef.current;
if (!scrollElement || previousScrollHeight.current === 0) return;
// 延迟执行,确保虚拟滚动已重新渲染并测量了新高度
const timer = setTimeout(() => {
const currentScrollHeight = scrollElement.scrollHeight;
const heightDifference = currentScrollHeight - previousScrollHeight.current;
// 如果高度增加了(说明上一页数据已加载),调整滚动位置
if (heightDifference > 0) {
console.log('%c📜 [位置保持] 调整滚动位置', 'color: #10B981; font-weight: bold;', {
previousHeight: previousScrollHeight.current,
currentHeight: currentScrollHeight,
heightDifference,
newScrollTop: scrollElement.scrollTop + heightDifference
});
// 调整 scrollTop使用户看到的内容位置不变
scrollElement.scrollTop += heightDifference;
// 重置记录
previousScrollHeight.current = 0;
}
}, 300);
return () => clearTimeout(timer);
}, [events.length]); // 监听 events 变化,加载上一页后会增加 events 数量
// 错误指示器(同行显示)
const renderErrorIndicator = () => {
if (!error) return null;