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

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)当作对象遍历,需排查以下点:

  1. 确认所有Getter返回的类型与前端状态定义一致,避免将对象类型字段返回为数值
  2. 检查getters: true是否导致字段被转换为非预期类型,可暂时关闭该选项测试
  3. 核对前端状态切片的类型定义,确保与后端返回数据结构匹配

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 19:32:45