如何用NodeJS、MongoDB、Pug实现无刷新多Ajax更新.todos?
实现目标拆解
你当前已经完成了「点击复选框发送PUT请求更新数据库done状态」的功能,剩下的只需要打通两个环节:
- 让更新数据的PUT接口返回渲染好的局部todo列表HTML
- 前端在请求成功后,用返回的HTML替换掉原来的
.todos容器内容,全程无需刷新页面
后端改造步骤
1. 抽离可复用的todo列表查询逻辑
原来的代码中todo接口的聚合查询没有做用户过滤,会把全库所有用户的todo都拉取排序,属于潜在逻辑bug,抽成公共函数后顺便修复:
const mongoose = require('mongoose'); const User = mongoose.model('User'); // 公共方法:获取当前用户按到期时间排序后的todo列表 async function getSortedUserTodos(userId) { return User.aggregate([ { $match: { _id: new mongoose.Types.ObjectId(userId) } }, // 匹配当前登录用户,避免拉取其他用户数据 { $unwind: '$todos' }, { $sort: { 'todos.dueDate': 1 } }, { $group: { _id: '$_id', todos: { $push: '$todos' } } }, ]); } exports.todo = async (req, res) => { const user = await User.findById(req.user._id); if (user.todos.length) { const updatedUser = await getSortedUserTodos(req.user._id); res.render('todos', { updatedUser: updatedUser[0].todos }); } else { res.render('layout'); } }; exports.addTodo = async (req, res) => { // 原有新增todo的逻辑保持不变 const add = { todos: { task: req.body.task, dueDate: req.body.dueDate, dueTime: req.body.dueTime, }, }; await User.findOneAndUpdate( { _id: req.user._id }, { $push: add }, { new: true } ).exec(); req.flash('success', 'saved'); res.redirect('/'); }; exports.checkStatus = async (req, res) => { // 原有数据库更新逻辑 await User.findOneAndUpdate( { todos: { $elemMatch: { _id: req.params.id } } }, { $set: { 'todos.$.done': req.body.done } } ); // 新增:拉取更新后的最新todo列表 const updatedUser = await getSortedUserTodos(req.user._id); // 新增:返回纯局部HTML片段,不套公共页面布局 res.render('todos', { updatedUser: updatedUser[0].todos, layout: false }); };
注意:res.render加layout: false参数,是为了避免Pug默认套入全局公共布局(导航、页脚等),我们只需要返回.todos区域对应的纯HTML结构。如果你的项目没有配置公共布局引擎,加这个参数也不会有副作用。
前端改造步骤
修改客户端的main.js,给已有的ajax请求补上成功回调逻辑,拿到返回的HTML后直接替换容器内容,不需要手动操作DOM移动todo项:
$('.todo--checkbox').change(function () { const todoId = $(this).data('value'); const isChecked = this.checked; const $checkbox = $(this); $.ajax({ url: `/todo/${todoId}`, type: 'PUT', data: { done: isChecked }, // 请求成功后替换容器内容 success: function (renderedHtml) { $('.todos').html(renderedHtml); }, // 可选错误兜底:请求失败时把复选框状态改回去,避免和数据库状态不一致 error: function () { $checkbox.prop('checked', !isChecked); alert('更新失败,请稍后重试'); } }); });
实现优势
- 完全复用原有Pug模板逻辑,不需要在前端硬编码拼接todo卡片的DOM结构,不会出现样式、渲染逻辑不一致的问题
- 每次替换整个
.todos容器时,后端会根据最新的done状态自动把待办项分到「未完成」「已完成」两个区域,不需要手动写DOM移动的逻辑 - 原有Pug模板不需要做任何修改,GET全页面访问、AJAX局部更新用的是同一套渲染逻辑,行为完全一致
内容的提问来源于stack exchange,提问作者AmAn KumAr
相关产品推荐
相关产品推荐

