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

Mongoose pre save钩子报错:类型“save”无法分配给参数类型

Mongoose pre("save")钩子TypeScript类型错误问题排查

问题描述

我正在使用Mongoose为应用创建用户模型,参考官方文档编写了代码,但在设置文档保存的pre钩子时,"save"处出现TypeScript错误。

错误提示

No overload matches this call.
  The last overload gave the following error.
    Argument of type '"save"' is not assignable to parameter of type 'RegExp | "insertMany"'.ts(2769)

相关代码

import { NextFunction } from 'express';
import { Schema, model } from 'mongoose';
const bcrypt = require("bcrypt")

export interface IUser {
    email: string
    password: string
    username: string
}

const UserSchema = new Schema<IUser>(
    {
        email: { type: String, required: true },
        password: { type: String, required: true, minlength: 5, hide: true },
        username: { type: String, required: true, minlength: 2 },
    },
)

UserSchema.pre("save", async function(next: NextFunction) {
    const user = this;
    if(!user.isModified("password")){
        next();
    }
    bcrypt.genSalt(10, (err: Error, salt: string) => {
        if(err) {
            return next(err);
        }
        bcrypt.hash(user.password, salt, (err: Error, hash: string) => {
            if(err){
                return next(err);
            }
            user.password = hash;
            next();
        })
    })
});

UserSchema.methods.comparePassword = function (password: string, cb: Function) {
    const user = this;
    bcrypt.compare(password, user.password, (err: Error, isMatch: boolean) => {
        if(err) {
            return cb(err);
        }
        cb(null, isMatch);
    })
}

const User = model<IUser>("User", UserSchema);
export default User

问题原因及解决方法

核心原因

  1. async函数与回调混用导致类型冲突:声明了async的pre钩子,但内部使用bcrypt的回调式API,TypeScript无法正确推断next函数的类型匹配。
  2. Schema泛型类型不完整:仅传入IUser作为Schema的泛型参数,缺少Mongoose文档内置方法(如isModified)的类型定义,引发类型推断异常。
  3. 字段配置错误:password字段的hide不是Mongoose内置选项,会导致类型识别异常。

分步解决

  1. 统一使用async/await风格重构pre钩子
    替换bcrypt的回调API为Promise版本,同时明确this的类型:
UserSchema.pre("save", async function(next: NextFunction) {
  const user = this as IUser & { isModified: (path: string) => boolean };
  // 密码未修改则直接跳过
  if (!user.isModified("password")) {
    return next();
  }
  try {
    // 生成盐并哈希密码
    const salt = await bcrypt.genSalt(10);
    user.password = await bcrypt.hash(user.password, salt);
    next();
  } catch (err) {
    // 捕获错误并传递给next
    next(err as Error);
  }
});
  1. 完善Schema的泛型定义
    使用Mongoose提供的HydratedDocument包含文档内置方法类型,同时修正字段配置:
import { Schema, model, HydratedDocument } from 'mongoose';

export interface IUser {
  email: string;
  password: string;
  username: string;
}

// 生成包含Mongoose文档方法的类型
type UserDocument = HydratedDocument<IUser>;

const UserSchema = new Schema<UserDocument>({
  email: { type: String, required: true },
  password: { type: String, required: true, minlength: 5, select: false }, // 替换hide为内置的select: false
  username: { type: String, required: true, minlength: 2 },
});
  1. 优化comparePassword方法的类型
    替换回调风格为Promise风格,避免模糊的Function类型:
UserSchema.methods.comparePassword = async function(password: string): Promise<boolean> {
  return bcrypt.compare(password, this.password);
};
  1. 确认Mongoose版本兼容性
    确保使用Mongoose v6及以上版本(v6+内置完善的TypeScript支持,无需单独安装@types/mongoose),旧版本建议升级到最新稳定版。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 05:05:41