Mongoose模型存在性检查时丢失TypeScript类型推断的问题
我太懂这种两难的处境了!用TypeScript结合Mongoose开发时,为了避免热重载导致的重复定义模型崩溃问题,咱们会加个模型存在性检查,但结果却丢了TypeScript最香的类型推断——查个用户返回any,写代码完全没提示,太闹心了😂
问题根源其实很简单:mongoose.models的默认类型是带有索引签名的对象,它的属性值默认是any类型,TypeScript没办法自动推断出mongoose.models.User和我们手动创建的User模型是同一个类型,所以用||运算符的时候,整个表达式的类型就被any“污染”了。
下面给你几个实用的解决办法,既解决热重载崩溃,又能保住类型推断:
方法一:手动给已有模型断言类型
先定义好用户文档的类型,然后给mongoose.models.User断言成我们的模型类型:
import mongoose from "mongoose"; // 定义用户的属性接口 interface User { email: string; // 你的其他属性... } // 结合mongoose的Document类型,得到完整的用户文档类型 type UserDocument = mongoose.Document & User; const userSchema = new mongoose.Schema<User>({ email: { type: String, unique: true, required: true }, // 其他属性配置... }); // 给已存在的模型断言类型,让TypeScript识别它的类型 export const UserModel = (mongoose.models.User as mongoose.Model<UserDocument>) || mongoose.model<UserDocument>("User", userSchema);
这样TypeScript就明确知道,不管是已有的模型还是新创建的模型,都是我们定义的mongoose.Model<UserDocument>类型,后续调用findOne这类方法时,返回值就会正确推断为UserDocument | null,再也不是any了。
方法二:用类型守卫做更严谨的判断
如果你想更严谨一点,可以写一个类型守卫函数,明确告诉TypeScript某个模型是不是我们的User模型:
import mongoose from "mongoose"; interface User { email: string; // 其他属性... } type UserDocument = mongoose.Document & User; const userSchema = new mongoose.Schema<User>({ email: { type: String, unique: true, required: true }, // 其他属性... }); // 类型守卫函数:判断传入的模型是否是User模型 function isUserModel(model: unknown): model is mongoose.Model<UserDocument> { return Boolean(model && typeof model === "object" && (model as { modelName?: string }).modelName === "User"); } // 用类型守卫做判断,TypeScript会自动推导类型 export const UserModel = isUserModel(mongoose.models.User) ? mongoose.models.User : mongoose.model<UserDocument>("User", userSchema);
这种方式通过自定义的类型守卫,让TypeScript在条件成立时确定模型的类型,逻辑更清晰,也更安全。
方法三:复用临时模型的类型
还有一种更简洁的方式,先创建一次模型拿到正确的类型,再复用这个类型做断言:
import mongoose from "mongoose"; interface User { email: string; // 其他属性... } type UserDocument = mongoose.Document & User; const userSchema = new mongoose.Schema<User>({ email: { type: String, unique: true, required: true }, // 其他属性... }); // 先创建临时模型,拿到正确的类型 const _tempUserModel = mongoose.model<UserDocument>("User", userSchema); // 复用临时模型的类型做断言 export const UserModel = (mongoose.models.User as typeof _tempUserModel) || _tempUserModel;
这个方法的好处是不用重复写mongoose.Model<UserDocument>,直接用typeof复用临时模型的类型,代码更简洁。
不管用哪种方法,核心都是帮TypeScript补上它无法自动推断的类型信息,让它知道mongoose.models.User和我们创建的模型是同一个类型,这样就能兼顾热重载的稳定性和TypeScript的类型推断啦。
备注:内容来源于stack exchange,提问作者taxfree

