如何在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
相关产品推荐
相关产品推荐

