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

如何在Express.js中用Mongoose分页加载子数组评论?

实现Item评论的分页加载(最新10条+加载更多)

现有数百条Item数据,每条Item包含评论,评论包含author、content、likes、replies字段。由于每条Item可能有数百条评论,所以将评论ID嵌入Item文档的子数组中,Schema定义如下:

const itemSchema = new mongoose.Schema(
  {
    name: {
      type: String,
      required: true,
      trim: true,
      index: true,
    },
    author: {
      type: mongoose.Schema.Types.ObjectId,
      ref: "User",
      required: true,
    },
    comments: [{ type: mongoose.Schema.Types.ObjectId, ref: "Comment" }],    
  },
  { timestamps: true }
);

const commentSchema = new mongoose.Schema(
  {
    author: {
      type: mongoose.Schema.Types.ObjectId,
      ref: User,
      required: true,
    },
    content: {
      type: String,
      required: true,
      trim: true,
    },
    likes: {
      type: [{ type: mongoose.Schema.Types.ObjectId, ref: User }],
      default: Array,
    },
    replies: { type: [this], default: Array },
  },
  { timestamps: true }
);

以下是在Express.js中用Mongoose实现「加载最新10条评论+点击加载更多」的方案:

1. 优化评论存储顺序

创建新评论时,将评论ID插入到Item的comments数组头部,确保数组内的ID顺序是最新在前,最旧在后,方便后续分页读取:

// 创建评论的示例接口
app.post('/items/:itemId/comments', async (req, res) => {
  const { itemId } = req.params;
  const { content } = req.body;
  const userId = req.user._id; // 假设已通过认证中间件获取当前用户ID

  // 创建评论文档
  const comment = new Comment({
    author: userId,
    content
  });
  await comment.save();

  // 将评论ID插入到Item的comments数组头部
  await Item.findByIdAndUpdate(
    itemId,
    { $push: { comments: { $each: [comment._id], $position: 0 } } },
    { new: true }
  );

  res.status(201).json(comment);
});

2. 获取Item及最新10条评论

设计接口返回Item详情、最新10条评论,同时返回是否还有更多评论的标识:

// 获取Item详情及最新评论接口
app.get('/items/:itemId', async (req, res) => {
  const { itemId } = req.params;
  const pageSize = 10;

  // 查询Item基础信息
  const item = await Item.findById(itemId).lean();
  if (!item) {
    return res.status(404).json({ message: 'Item不存在' });
  }

  // 判断是否有更多评论
  const hasMore = item.comments.length > pageSize;

  // 取前10条评论ID,批量查询评论详情并关联用户信息
  const targetCommentIds = item.comments.slice(0, pageSize);
  const comments = await Comment.find({ _id: { $in: targetCommentIds } })
    .populate('author', 'username avatar') // 仅返回用户必要字段,减少数据量
    .sort({ createdAt: -1 })
    .lean();

  res.json({
    item,
    comments,
    hasMore
  });
});

3. 加载更多评论接口

当用户点击「加载更多」时,通过已加载评论数量作为偏移量,查询下一页的旧评论:

// 加载更多评论接口
app.get('/items/:itemId/comments/load-more', async (req, res) => {
  const { itemId } = req.params;
  const { skip } = req.query; // skip为已加载的评论数量,如第一次加载后传10
  const pageSize = 10;

  const item = await Item.findById(itemId).lean();
  if (!item) {
    return res.status(404).json({ message: 'Item不存在' });
  }

  // 计算要获取的评论ID范围
  const startIdx = parseInt(skip);
  const endIdx = startIdx + pageSize;
  const targetCommentIds = item.comments.slice(startIdx, endIdx);

  if (targetCommentIds.length === 0) {
    return res.json({ comments: [], hasMore: false });
  }

  // 查询评论详情
  const comments = await Comment.find({ _id: { $in: targetCommentIds } })
    .populate('author', 'username avatar')
    .sort({ createdAt: -1 })
    .lean();

  // 判断是否还有更多评论
  const hasMore = item.comments.length > endIdx;

  res.json({
    comments,
    hasMore
  });
});

4. 客户端配合逻辑

  • 首次请求/items/:itemId,获取最新10条评论,记录已加载数量为10,根据hasMore状态决定是否显示「加载更多」按钮。
  • 点击按钮时,若hasMore为true,请求/items/:itemId/comments/load-more?skip=10,将返回的评论追加到列表中,更新已加载数量为20,同步hasMore状态。
  • 当hasMore为false时,隐藏「加载更多」按钮。

大数量评论的性能优化

若Item评论数量极大(上万条),用数组切片的方式会有性能瓶颈,建议改用游标分页:通过记录最后一条加载的评论的createdAt和_id,精准过滤更早的评论,避免大数组切片的开销:

// 游标分页版加载更多接口
app.get('/items/:itemId/comments/load-more', async (req, res) => {
  const { itemId } = req.params;
  const { lastCreatedAt, lastCommentId } = req.query;
  const pageSize = 10;

  const item = await Item.findById(itemId).lean();
  if (!item) {
    return res.status(404).json({ message: 'Item不存在' });
  }

  // 构建查询条件:获取比最后一条加载的评论更早的内容
  const query = {
    _id: { $in: item.comments },
    createdAt: { $lt: new Date(lastCreatedAt) }
  };
  // 处理同时间创建的评论,通过ID进一步排序
  if (lastCommentId) {
    query.$or = [
      { createdAt: { $lt: new Date(lastCreatedAt) } },
      { createdAt: new Date(lastCreatedAt), _id: { $lt: lastCommentId } }
    ];
  }

  const comments = await Comment.find(query)
    .populate('author', 'username avatar')
    .sort({ createdAt: -1, _id: -1 })
    .limit(pageSize)
    .lean();

  const hasMore = comments.length === pageSize;

  res.json({
    comments,
    hasMore,
    // 返回当前页最后一条评论的标识,供下一次请求使用
    lastCreatedAt: comments.length > 0 ? comments[comments.length - 1].createdAt : null,
    lastCommentId: comments.length > 0 ? comments[comments.length - 1]._id : null
  });
});

内容的提问来源于stack exchange,提问作者Yusuf Isakov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 11:25:14