Express TypeScript中Mongoose pre find钩子类型错误排查
解决Mongoose + TypeScript中pre(/^find/)钩子的类型错误
问题背景
使用Express.js + TypeScript + Mongoose开发,User模型代码如下:
import mongoose, { Schema, Document, Model } from 'mongoose'; import crypto from 'crypto'; import validator from 'validator'; import bcrypt from 'bcryptjs'; import roles from '../constant/role'; export interface IUser extends Document { firstName: string; lastName: string; phone: string; email: string; password: string; role: string; dateOfBirth?: Date; emailVerified?: boolean; phoneVerified?: boolean; updatedBy?: Schema.Types.ObjectId; createdBy?: Schema.Types.ObjectId; passwordChangedAt?: Date; passwordResetToken?: string; passwordResetExpires?: Date; emailVerificationToken?: string; emailVerificationExpires?: Date; active?: boolean; fullName?: string; } export interface IUserMethods { correctPassword( candidatePassword: string, userPassword: string ): Promise<boolean>; changedPasswordAfter(jwtTimeStamp: number): boolean; createPasswordResetToken(): string; createEmailVerificationToken(): string; } type UserModel = Model<IUser, {}, IUserMethods>; const UserSchema = new Schema<IUser, UserModel, IUserMethods>( { firstName: { type: String, required: [true, 'First Name is required'], trim: true, }, lastName: { type: String, trim: true }, phone: { type: String, trim: true, unique: true, required: [true, 'Phone is required'], }, email: { type: String, required: [true, 'Email is required'], unique: true, validate: [validator.isEmail, 'Email is not valid'], trim: true, }, password: { type: String, required: [true, 'Password is required'], select: false, }, dateOfBirth: { type: Date }, emailVerified: { type: Boolean, default: false }, phoneVerified: { type: Boolean, default: false }, role: { type: String, enum: [roles.SUPER_ADMIN, roles.ADMIN, roles.STUDENT, roles.GUEST], default: roles.GUEST, }, updatedBy: { type: Schema.Types.ObjectId, ref: 'User' }, createdBy: { type: Schema.Types.ObjectId, ref: 'User' }, passwordChangedAt: { type: Date }, passwordResetToken: String, passwordResetExpires: Date, emailVerificationToken: String, emailVerificationExpires: Date, active: { type: Boolean, default: true, select: false, }, }, { timestamps: true, versionKey: false, } ); UserSchema.pre(/^find/, async function (next) { const user = this; user.find({ active: { $ne: false } }); next(); }); const User = mongoose.model<IUser, UserModel>('User', UserSchema); export default User;
在VSCode中,user.find({ active: { $ne: false } })这行出现类型错误:
Property 'find' does not exist on type '(Document<unknown, {}, FlatRecord<IUser>> & Omit<FlatRecord<IUser> & { _id: ObjectId; }, keyof IUserMethods> & IUserMethods) | Query<...>'. Property 'find' does not exist on type 'Document<unknown, {}, FlatRecord<IUser>> & Omit<FlatRecord<IUser> & { _id: ObjectId; }, keyof IUserMethods> & IUserMethods'.ts(2339)
问题根源
- 类型推断歧义:Mongoose的
pre(/^find/)钩子中,this的类型被推断为Query | Document的联合类型。对于find类查询钩子,this应该是Query对象,但TypeScript无法确定具体类型,导致报错。 - 错误的方法调用:代码中试图在
this上调用find(),但正确逻辑是修改当前查询的条件,而非调用新的find方法。
解决步骤
步骤1:明确this的类型
在钩子函数中,通过类型断言指定this为Mongoose的Query类型:
import { Query } from 'mongoose'; UserSchema.pre(/^find/, async function (this: Query<IUser[], IUser>, next) { this.find({ active: { $ne: false } }); next(); });
步骤2:修正查询逻辑
直接在this(当前查询对象)上追加条件即可,无需将this赋值给user变量,避免类型混淆:
UserSchema.pre(/^find/, async function (this: Query<IUser[], IUser>, next) { this.find({ active: { $ne: false } }); next(); });
关于Document<unknown>提示的解释
该提示是因为TypeScript无法确定联合类型中Document部分的具体泛型参数,当this被推断为Document | Query时,Document的泛型参数默认是unknown,导致类型提示模糊。通过明确this的类型为Query,即可消除这个模糊提示。
内容的提问来源于stack exchange,提问作者Vivek V Dwivedi
相关产品推荐
相关产品推荐

