You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

分页课程Redux状态重复问题排查求助

无限滚动分页数据重复问题排查与修复

核心问题分析

从你的代码来看,数据重复大概率是同一分页请求被多次触发导致的,结合无限滚动场景,主要有几个触发点:

  • 滚动事件连续触发(比如快速滚动到底部),导致loadClasses被多次调用,而此时前一次请求还未完成,_page状态没更新,会重复请求同一页数据,最终concat到Redux状态里。
  • 缺少请求锁机制,没有在请求进行中阻止新的请求。
  • Redux-persist的状态恢复时机可能晚于组件初始化的清空操作,导致清空后又被持久化的数据覆盖。

具体修复方案

1. 给请求加上加载锁,阻止重复调用

在自定义hook里,利用Redux的isLoading状态或者本地状态,确保同一时间只有一个分页请求在执行:

export default function useInfiniteClasses(
  classesRef: any,
  user: any,
  _page: any,
  setPage: any
) {
  const dispatch = useAppDispatch();
  const { isLoading, lastPage } = useAppSelector((state: RootState) => state.classes);
  const [isRequesting, setIsRequesting] = useState(false);

  const loadClasses = async () => {
    // 拦截条件:加载中、无用户信息、已到最后一页
    if (isLoading || isRequesting || !user || _page > lastPage) return;

    setIsRequesting(true);
    try {
      if (user?.role === "student") {
        await dispatch(paginateStudentClasses({
          studentId: user?.clientId,
          page: _page,
        }));
      } else if (user?.role === "teacher") {
        await dispatch(paginateTeacherClasses({
          teacherId: user?.clientId,
          page: _page,
        }));
      }

      setPage(prev => prev + 1);
    } catch (error) {
      console.error(error);
    } finally {
      setIsRequesting(false);
    }
  };

  const handleScroll = () => {
    if (isLoading || isRequesting || _page > lastPage) return;
    
    if (Math.floor(classesRef?.current?.scrollLeft + classesRef?.current?.clientWidth) === classesRef?.current?.scrollWidth) {
      loadClasses();
    }
  };

  return { handleScroll, loadClasses };
}

2. 调整Redux-persist清空时机

如果持久化的状态在组件初始化后才恢复,会导致你调用的emptyPaginateClasses被覆盖。可以等待persist完成后再执行初始化操作:

// 组件中添加persist状态监听
const { _persist } = useAppSelector((state: RootState) => state);
const initialized = useRef(false);

useEffect(() => {
  // 确保persist恢复完成且只初始化一次
  if (_persist?.rehydrated && !initialized.current) {
    dispatch(emptyPaginateClasses());
    loadClasses();
    initialized.current = true;
  }
}, [_persist?.rehydrated, dispatch, loadClasses]);

3. 可选:Redux状态更新时去重

如果接口可能返回重复数据,可以在Redux reducer里添加去重逻辑,避免重复数据被存入:

.addCase(paginateStudentClasses.fulfilled, (state: any, action: any) => {
    console.count("paginate classes!");
    const { classes } = action.payload;
    // 根据课程ID过滤已存在的项
    const uniqueClasses = classes.filter(cls => !state.paginateClasses.some(item => item.id === cls.id));
    state.paginateClasses = state.paginateClasses.concat(uniqueClasses);
    state.lastPage = action.payload.lastPage;
    state.isLoading = false;
})

4. 检查组件是否重复挂载

如果组件被父组件频繁触发卸载/挂载,会导致初始化的useEffect多次执行。用useRef标记是否已经初始化过,避免重复执行:

const initialized = useRef(false);

useEffect(() => {
  if (!initialized.current) {
    dispatch(emptyPaginateClasses());
    loadClasses();
    initialized.current = true;
  }
}, []);

内容的提问来源于stack exchange,提问作者mohsin syed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 09:57:18