Mongoose中pre钩子不存在报错,如何检测子文档删除时的文档变更?
解决Mongoose Pre钩子无法启用及TypeScript类型报错问题
嘿,看你的代码,你在使用Mongoose pre钩子和TypeScript的时候踩了几个典型的坑,我来帮你理清楚怎么解决:
首先说你代码里的核心问题
- Pre钩子不能在请求处理里临时加:Mongoose的中间件(包括pre钩子)必须在模型编译完成前就注册到schema上,你现在在delete接口里才注册
pre('save'),这时候模型早就生成好了,钩子根本不会生效。 findOneAndUpdate不触发save钩子:这个方法是直接在数据库层面执行更新操作,不会走模型的save流程,所以哪怕你注册了pre('save'),也不会被触发。- TypeScript类型没配对:你的
Category接口没继承Mongoose的Document,所以TypeScript不知道isModified()这些内置方法的存在,自然会报属性不存在的错。
接下来是修正后的完整代码
第一步:完善Schema和类型定义(category.schema.ts)
先把类型补全,然后在模型编译前注册好钩子:
import mongoose, { Schema, Model, Document } from 'mongoose'; // 先定义子文档Item的类型 interface Item { item_id: number; item_name: string; } // 让Category接口继承Document,这样就能拿到Mongoose的所有内置方法 interface Category extends Document { category_id: number; name: string; icon_url: string; items_quantity: number; items: Item[]; } const categorySchema = new Schema<Category>({ category_id: { type: Number, required: true, unique: true }, name: { type: String, required: true, unique: true }, icon_url: { type: String, required: true }, items_quantity: { type: Number, required: true }, items: [ { item_id: { type: Number, required: true, unique: true }, item_name: { type: String, required: true } } ] }); // 重点:在模型编译前注册pre钩子! categorySchema.pre('save', function(next) { // 这里的this就是当前的Category文档,类型已经自动匹配了 if (this.isModified()) { console.log('文档有变更!'); // 顺便可以在这里更新items_quantity,比如删除item后自动减1 this.items_quantity = this.items.length; } else { console.log('文档没变化~'); } next(); // 一定要调用next()让流程继续 }); // 最后编译模型 const Category: Model<Category> = mongoose.model<Category>('Category', categorySchema); export default Category;
第二步:修正删除接口的逻辑(路由文件)
因为findOneAndUpdate不触发save钩子,所以我们改成“先查文档→修改内容→手动save”的流程,这样就能触发pre钩子了:
import Category from "../schemas/category.schema"; import { Router } from 'express'; const router = Router(); router.delete('/:category/:item', async (req, res) => { try { // 1. 先找到对应的分类文档 const categoryId = parseInt(req.params.category); const itemId = parseInt(req.params.item); const category = await Category.findOne({ category_id: categoryId }); if (!category) { return res.status(404).send('找不到目标分类'); } // 2. 移除指定的item category.items = category.items.filter(item => item.item_id !== itemId); // 3. 调用save,这时候就会触发我们之前注册的pre钩子了 const updatedCategory = await category.save(); res.status(200).send({ message: `Item ${itemId} 删除成功`, updated: updatedCategory }); } catch (err) { res.status(500).send(`删除失败:${(err as Error).message}`); } }); export default router;
额外补充:如果想直接用findOneAndUpdate怎么办?
如果你不想改查询逻辑,也可以用pre('findOneAndUpdate')钩子,但要注意这个钩子的this是查询对象,不是文档本身,所以没法直接调用isModified(),示例如下:
// 在category.schema.ts里注册这个钩子 categorySchema.pre('findOneAndUpdate', function(next) { const update = this.getUpdate() as any; console.log('要执行的更新操作:', update); // 这里可以做一些逻辑,比如更新items_quantity,但需要自己计算或者额外查询 next(); });
不过这种方式没法直接拿到文档的完整数据,如果你需要基于文档当前状态做操作(比如更新items_quantity),还是推荐先查询再save的方式更直观。
内容的提问来源于stack exchange,提问作者MWN
相关产品推荐
相关产品推荐

