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

ReactJS与Mongoose无限滚动问题:置顶reach-up-ad及修复重复加载

问题修复与需求实现方案

1. 修复「无法将reach-up-ad置顶」的问题

当前前端仅对单批次加载的数据排序,导致新获取的reach-up-ad只能排在当前批次顶部,无法插入整个列表最前方。更可靠的方案是后端优先返回reach-up-ad数据,从根源保证排序逻辑:

修改Mongoose后端代码,调整排序规则,让reach-up-ad优先返回,再按创建时间倒序:

export async function getMobilePosts(skips: any) {
  try{
    const skip = skips ? Number(skips) : 0;
    const DEFAULT_LIMIT = 10;
    return await Post.find({expiredAt: {$gte: currentDate},status: 'live'},
                          "features.subCategory features.condition swapType.type swapType.price createdAt features.mainCategory title location.district location.location photos adPromote status description postedBy notes")
                          .skip(skip)
                          .limit(DEFAULT_LIMIT)
                          // 优先按adPromote排序,reach-up-ad前置,再按创建时间倒序
                          .sort({ $expr: { $cond: [{ $eq: ["$adPromote", "reach-up-ad"] }, 0, 1] }, _id: -1 });
  } catch(error: any) {
    throw new Error(error as any);
  }
}

前端可移除原有排序逻辑,简化代码:

const filterData = data?.filter(obj => obj.adPromote == "free-ad" || obj.adPromote == "reach-up-ad")
.map(obj => {
    return {
        ...obj,
        title: `${obj.title} (${obj.features.condition})`,
        location: `Posted On ${new Date(obj.createdAt).toDateString()}`
    }
});

2. 修复「点击Load more重复加载相同数据」的问题

当前用前端过滤后的posts.length作为分页skip值,因过滤会减少数据量,导致下一次请求的skip重复之前的范围。解决方案是维护独立的totalFetched状态,记录从后端请求过的总数据条数:

// 新增状态
const [totalFetched, setTotalFetched] = useState(0);
const [isLoading, setIsLoading] = useState(false); // 防止并发请求

// 修改fetchPosts逻辑
const fetchPosts = async () => {
  if (isLoading) return;
  setIsLoading(true);
  try {
    const { data } = await getMobilePosts(totalFetched); // 用totalFetched作为skip参数
    const filterData = data?.filter(obj => obj.adPromote == "free-ad" || obj.adPromote == "reach-up-ad")
    .map(obj => {
        return {
            ...obj,
            title: `${obj.title} (${obj.features.condition})`,
            location: `Posted On ${new Date(obj.createdAt).toDateString()}`
        }
    });
    setPosts(existing => [...existing, ...filterData]);
    setTotalFetched(prev => prev + data.length); // 更新已请求的后端总条数
  } catch (err) {
    console.error('加载失败:', err);
  } finally {
    setIsLoading(false);
  }
}

3. 实现「移除Load more按钮,窗口滚动自动加载」

移除手动按钮,添加滚动监听,判断滚动到接近底部时自动触发加载:

// 初始加载
useEffect(() => {
  fetchPosts();
}, []);

// 滚动监听逻辑
useEffect(() => {
  const handleScroll = () => {
    // 判断是否滚动到距离底部200px以内,且不在加载中
    const isNearBottom = window.innerHeight + document.documentElement.scrollTop 
      >= document.documentElement.offsetHeight - 200;
    if (isNearBottom && !isLoading) {
      fetchPosts();
    }
  };

  window.addEventListener('scroll', handleScroll);
  return () => window.removeEventListener('scroll', handleScroll);
}, [isLoading]);

// 修改JSX,移除Load more按钮,添加加载提示
return (
    <div>
      {posts?.map((data) => {
        return (
            <ExchangeItemWideCard
            key={data._id} // 用数据ID作为key,避免索引导致的渲染问题
            id={data._id}
            title={data.title}
            location={data.location}
            />
        )
      })}
      {isLoading && <div style={{textAlign:'center', padding:'1rem'}}>加载中...</div>}
    </div>
)

完整前端代码整合

import { useState, useEffect } from 'react';
import ExchangeItemWideCard from './ExchangeItemWideCard';

const PostList = () => {
  const [posts, setPosts] = useState([]);
  const [totalFetched, setTotalFetched] = useState(0);
  const [isLoading, setIsLoading] = useState(false);

  const fetchPosts = async () => {
    if (isLoading) return;
    setIsLoading(true);
    try {
      const { data } = await getMobilePosts(totalFetched);
      if (!data || data.length === 0) return;

      const filterData = data.filter(obj => 
        obj.adPromote === "free-ad" || obj.adPromote === "reach-up-ad"
      ).map(obj => ({
        ...obj,
        title: `${obj.title} (${obj.features.condition})`,
        location: `Posted On ${new Date(obj.createdAt).toDateString()}`
      }));

      setPosts(existing => [...existing, ...filterData]);
      setTotalFetched(prev => prev + data.length);
    } catch (error) {
      console.error('加载帖子失败:', error);
    } finally {
      setIsLoading(false);
    }
  };

  useEffect(() => {
    fetchPosts();
  }, []);

  useEffect(() => {
    const handleScroll = () => {
      const isNearBottom = window.innerHeight + document.documentElement.scrollTop 
        >= document.documentElement.offsetHeight - 200;
      if (isNearBottom && !isLoading) {
        fetchPosts();
      }
    };

    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [isLoading]);

  return (
    <div>
      {posts.map((data) => (
        <ExchangeItemWideCard
          key={data._id}
          id={data._id}
          title={data.title}
          location={data.location}
        />
      ))}
      {isLoading && <div style={{ textAlign: 'center', padding: '1rem' }}>加载中...</div>}
    </div>
  );
};

export default PostList;

关键优化点总结

  • 后端排序确保reach-up-ad始终置顶,避免前端分批排序的局限性
  • 用totalFetched记录后端请求总量,彻底解决重复加载问题
  • 滚动监听实现自动加载,移除手动按钮提升用户体验
  • 增加isLoading状态防止并发请求,用数据ID作为列表key提升渲染稳定性

内容的提问来源于stack exchange,提问作者asela daskon

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 03:25:07