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

如何在Mongoose回调函数中正确推断TypeScript类型?

Mongoose实例方法在TypeScript中类型推断失败的解决办法

问题背景

将Node.js项目迁移至TypeScript v4.9.4时,搭配Mongoose v6.9.0遇到类型推断异常:

  • 调用findOne()等查询方法的回调函数中,document参数的类型无法自动识别,即使显式指定为对应模型类型,也无法读取实例方法
  • 但调用带回调的create()方法时,类型推断完全正常
  • 按照Mongoose官方文档「Schemas: Instance Methods」「Statics and Methods in TypeScript」配置Schema后,问题依然存在

复现代码

user.model.ts

import { HydratedDocument, InferSchemaType, Schema, model } from "mongoose";

export type User = HydratedDocument<InferSchemaType<typeof userSchema>>;

const userSchema = new Schema({
  email: {
    type: String,
    unique: true,
    required: true,
  },
  name: {
    type: String,
    required: true,
  },
  hash: String,
  salt: String,
}, {
  methods: {
    setPassword(password: string) {
      // 盐值与哈希逻辑
    },
  },
  toJSON: { virtuals: true },
  toObject: { virtuals: true }
});

export const UserModel = model("User", userSchema);

auth.service.ts

import { CallbackError } from "mongoose";
import { User, UserModel } from "../models/user.model";

export function login(username, password) {
  UserModel.findOne({ email: username }, function (err: CallbackError, user: User) {
    // 报错:Property 'validPassword' does not exist on type ...
    if (!user.validPassword(password)) {
      // 处理密码错误
    }
  });
}

完整错误信息

Property 'validPassword' does not exist on type 'Document<unknown, any, { email: string; name: string; hash?: string | undefined; salt?: string | undefined; }> & { email: string; name: string; hash?: string | undefined; salt?: string | undefined; } & { ...; }'.

原因分析

Mongoose v6.x中,InferSchemaType生成的类型仅包含Schema定义的字段,不会自动关联实例方法;同时回调式查询方法的类型推导逻辑与create()存在差异,导致实例方法无法被TypeScript识别。

解决方案

方案1:手动定义接口关联实例方法

放弃InferSchemaType,先定义包含实例方法的接口,再让Schema和模型关联该接口,确保类型完整:

修改user.model.ts:

import { HydratedDocument, Schema, model, Model } from "mongoose";

// 定义包含字段和实例方法的核心接口
interface IUser {
  email: string;
  name: string;
  hash?: string;
  salt?: string;
  setPassword(password: string): void;
  validPassword(password: string): boolean; // 明确声明实例方法
}

// 关联模型与文档类型的接口
interface UserModel extends Model<IUser> {}

const userSchema = new Schema<IUser, UserModel>({
  email: {
    type: String,
    unique: true,
    required: true,
  },
  name: {
    type: String,
    required: true,
  },
  hash: String,
  salt: String,
}, {
  toJSON: { virtuals: true },
  toObject: { virtuals: true }
});

// 挂载实例方法
userSchema.methods.setPassword = function(password: string) {
  // 你的实现逻辑
};

userSchema.methods.validPassword = function(password: string): boolean {
  // 你的密码验证逻辑
  return true;
};

export type User = HydratedDocument<IUser>;
export const UserModel = model<IUser, UserModel>("User", userSchema);

修改后auth.service.ts中,user的类型会自动包含实例方法:

import { CallbackError } from "mongoose";
import { User, UserModel } from "../models/user.model";

export function login(username: string, password: string) {
  UserModel.findOne({ email: username }, function (err: CallbackError, user: User) {
    if (user && !user.validPassword(password)) {
      // 正常处理逻辑
    }
  });
}

方案2:改用async/await替代回调

Mongoose支持Promise风格调用,改用async/await可直接规避回调的类型推断问题,同时代码更简洁:

export async function login(username: string, password: string) {
  try {
    const user = await UserModel.findOne({ email: username });
    if (user && !user.validPassword(password)) {
      // 处理密码错误
    }
  } catch (err) {
    // 处理查询异常
  }
}

这种方式下,TypeScript能自动正确推断user的完整类型,包括所有实例方法。

补充说明

你提到的将实例方法抽离到单独服务模块是可行的替代方案,但如果希望保留Mongoose实例方法的写法,上述两种方案更贴合Mongoose的TypeScript最佳实践。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 02:10:15