Mongoose Getter/Setter问题:findOneAndUpdate不触发Setter
MERN栈+RTK Query下Mongoose Setter不触发及Virtual Getter报错问题
我在使用MERN栈结合RTK Query开发时遇到几个核心问题:
- 为Schema字段定义的Setter在调用
findOneAndUpdate或updateOne时完全不触发 - 尝试用Virtual Getter替代Setter生成字段时,即便添加了条件判断,仍会出现读取未定义变量的错误
- 当Schema配置
{getters: true}时,RTK端抛出错误:Cannot use 'in' operator to search for 'unit' in 40
相关代码片段
1. Setter定义及Schema配置
damage字段的Setter定义:
damage: { diceCount: Number, diceValue: Number, diceShort: { type: String, set: function () { let diceShortValue = null if (this.diceCount && this.diceValue) { diceShortValue = `${this.diceCount}d${this.diceValue}` } return diceShortValue }, }, versatileDiceValue: Number, versatileDiceCount: Number, bonusDamage: Number, damageType: { type: [ { id: { type: Schema.Types.ObjectId, ref: 'Builder' }, name: String, value: String, }, ], default: undefined, }, },
Schema末尾配置:
export const itemSchema = new Schema( { // 其他字段省略 }, { toJSON: { setters: true, minimize: true }, toObject: { setters: true, minimize: true }, } ) export default mongoose.model('Item', itemSchema)
2. API更新逻辑
后端更新接口代码:
export const updateItem = async (req, res) => { const { _id } = req.body if (!_id) { return res.status(400).json({ message: 'Item ID is required' }) } const updatedItem = req.body const item = await Item.findOneAndUpdate({ _id }, updatedItem, { new: true, }).exec() if (!item) { return res.status(400).json({ message: 'Item not found' }) } res.json(`'${item.value}' updated`) }
问题分析与解决方案
一、Setter不触发的解决
Mongoose字段Setter默认仅在直接修改文档实例时触发,findOneAndUpdate这类直接更新操作会绕开实例,因此不会调用Setter。有两种解决方式:
1. 改用实例更新
先查询出文档实例,修改后再保存,确保Setter被触发:
export const updateItem = async (req, res) => { const { _id } = req.body if (!_id) { return res.status(400).json({ message: 'Item ID is required' }) } const item = await Item.findById(_id).exec() if (!item) { return res.status(400).json({ message: 'Item not found' }) } // 逐个赋值触发子字段Setter,避免直接覆盖整个damage对象 Object.assign(item, req.body) await item.save() res.json(`'${item.value}' updated`) }
注意:若直接赋值item.damage = req.body.damage,会覆盖整个子对象,子字段Setter不会触发,需逐个修改子属性或用深度合并方式。
2. 使用预保存中间件
若必须使用findOneAndUpdate,可添加pre-save中间件手动计算diceShort:
itemSchema.pre('save', function(next) { if (this.damage?.diceCount && this.damage?.diceValue) { this.damage.diceShort = `${this.damage.diceCount}d${this.damage.diceValue}` } else { this.damage.diceShort = null } next() }) // 更新时需开启runValidators和context选项,触发中间件 const item = await Item.findOneAndUpdate({ _id }, updatedItem, { new: true, runValidators: true, context: 'query' }).exec()
二、Virtual Getter报错的解决
报错原因大概率是未判断父级字段damage是否存在,需补充层级判断:
itemSchema.virtual('damage.diceShort').get(function() { // 先判断damage是否存在,再校验子字段 if (this.damage && this.damage.diceCount && this.damage.diceValue) { return `${this.damage.diceCount}d${this.damage.diceValue}` } return null })
同时需在Schema配置中开启Virtual字段序列化:
{ toJSON: { virtuals: true, minimize: true }, toObject: { virtuals: true, minimize: true }, }
三、RTK端Cannot use 'in' operator报错解决
该错误是因为RTK Query尝试将数值类型(如示例中的40)当作对象遍历,需排查以下点:
- 确认所有Getter返回的类型与前端状态定义一致,避免将对象类型字段返回为数值
- 检查
getters: true是否导致字段被转换为非预期类型,可暂时关闭该选项测试 - 核对前端状态切片的类型定义,确保与后端返回数据结构匹配
内容的提问来源于stack exchange,提问作者MPiland
相关产品推荐
相关产品推荐

