乐闻世界logo
搜索文章和话题

Mongoose 如何与 TypeScript 结合使用?

2月22日 20:06

Mongoose 与 TypeScript 结合使用可以提供类型安全、更好的开发体验和代码提示。通过使用 Mongoose 的类型定义,可以在编译时捕获错误。

基本类型定义

定义 Schema 类型

typescript
import mongoose, { Schema, Document, Model } from 'mongoose'; // 定义文档接口 interface IUser extends Document { name: string; email: string; age: number; createdAt: Date; } // 定义 Schema const userSchema: Schema = new Schema({ name: { type: String, required: true }, email: { type: String, required: true, unique: true }, age: { type: Number, min: 0 }, createdAt: { type: Date, default: Date.now } }); // 创建模型类型 interface IUserModel extends Model<IUser> { findByEmail(email: string): Promise<IUser | null>; } // 创建模型 const User: IUserModel = mongoose.model<IUser, IUserModel>('User', userSchema);

使用类型化模型

创建文档

typescript
const user: IUser = new User({ name: 'John Doe', email: 'john@example.com', age: 25 }); await user.save();

查询文档

typescript
// 查询单个文档 const user: IUser | null = await User.findById(userId); // 查询多个文档 const users: IUser[] = await User.find({ age: { $gte: 18 } }); // 使用 lean() 返回普通对象 const plainUsers = await User.find().lean();

更新文档

typescript
const user: IUser | null = await User.findById(userId); if (user) { user.age = 26; await user.save(); } // 使用 findOneAndUpdate const updatedUser: IUser | null = await User.findOneAndUpdate( { email: 'john@example.com' }, { age: 26 }, { new: true } );

静态方法类型

定义静态方法

typescript
interface IUserModel extends Model<IUser> { findByEmail(email: string): Promise<IUser | null>; findAdults(): Promise<IUser[]>; countByAge(minAge: number): Promise<number>; } // 实现静态方法 userSchema.statics.findByEmail = function(email: string): Promise<IUser | null> { return this.findOne({ email }); }; userSchema.statics.findAdults = function(): Promise<IUser[]> { return this.find({ age: { $gte: 18 } }); }; userSchema.statics.countByAge = function(minAge: number): Promise<number> { return this.countDocuments({ age: { $gte: minAge } }); }; // 使用静态方法 const user = await User.findByEmail('john@example.com'); const adults = await User.findAdults(); const count = await User.countByAge(18);

实例方法类型

定义实例方法

typescript
interface IUser extends Document { name: string; email: string; age: number; getFullName(): string; isAdult(): boolean; updateAge(newAge: number): Promise<IUser>; } // 实现实例方法 userSchema.methods.getFullName = function(): string { return this.name; }; userSchema.methods.isAdult = function(): boolean { return this.age >= 18; }; userSchema.methods.updateAge = async function(newAge: number): Promise<IUser> { this.age = newAge; return this.save(); }; // 使用实例方法 const user = await User.findById(userId); if (user) { console.log(user.getFullName()); console.log(user.isAdult()); await user.updateAge(26); }

虚拟字段类型

定义虚拟字段

typescript
interface IUser extends Document { firstName: string; lastName: string; fullName: string; // 虚拟字段 } const userSchema: Schema = new Schema({ firstName: { type: String, required: true }, lastName: { type: String, required: true } }); // 定义虚拟字段 userSchema.virtual('fullName').get(function(this: IUser): string { return `${this.firstName} ${this.lastName}`; }); userSchema.virtual('fullName').set(function(this: IUser, value: string): void { const parts = value.split(' '); this.firstName = parts[0]; this.lastName = parts[1]; });

嵌套文档类型

定义嵌套 Schema

typescript
interface IAddress { street: string; city: string; state: string; zipCode: string; } interface IUser extends Document { name: string; email: string; address: IAddress; } const addressSchema: Schema = new Schema({ street: { type: String, required: true }, city: { type: String, required: true }, state: { type: String, required: true }, zipCode: { type: String, required: true } }); const userSchema: Schema = new Schema({ name: { type: String, required: true }, email: { type: String, required: true }, address: { type: addressSchema, required: true } });

数组字段类型

定义数组类型

typescript
interface IUser extends Document { name: string; tags: string[]; scores: number[]; } const userSchema: Schema = new Schema({ name: { type: String, required: true }, tags: [String], scores: [Number] });

关联类型

定义关联

typescript
interface IPost extends Document { title: string; content: string; author: mongoose.Types.ObjectId; } interface IUser extends Document { name: string; email: string; posts: mongoose.Types.ObjectId[]; } const postSchema: Schema = new Schema({ title: { type: String, required: true }, content: { type: String, required: true }, author: { type: Schema.Types.ObjectId, ref: 'User' } }); const userSchema: Schema = new Schema({ name: { type: String, required: true }, email: { type: String, required: true }, posts: [{ type: Schema.Types.ObjectId, ref: 'Post' }] }); // 使用 populate const user = await User.findById(userId).populate('posts');

中间件类型

定义中间件

typescript
import { HookNextFunction } from 'mongoose'; // Pre 中间件 userSchema.pre('save', function(this: IUser, next: HookNextFunction): void { this.email = this.email.toLowerCase(); next(); }); // Post 中间件 userSchema.post('save', function(this: IUser, doc: IUser): void { console.log('User saved:', doc.email); }); // 异步中间件 userSchema.pre('save', async function(this: IUser, next: HookNextFunction): Promise<void> { if (await emailExists(this.email)) { return next(new Error('Email already exists')); } next(); }); async function emailExists(email: string): Promise<boolean> { const count = await User.countDocuments({ email }); return count > 0; }

泛型模型

创建泛型模型

typescript
interface BaseModel extends Document { createdAt: Date; updatedAt: Date; } function createTimestampedModel<T extends Document>( name: string, schema: Schema ): Model<T & BaseModel> { schema.add({ createdAt: { type: Date, default: Date.now }, updatedAt: { type: Date, default: Date.now } }); schema.pre('save', function(this: T & BaseModel, next: HookNextFunction): void { this.updatedAt = new Date(); next(); }); return mongoose.model<T & BaseModel>(name, schema); } // 使用泛型模型 interface IUser extends Document { name: string; email: string; } const userSchema: Schema = new Schema({ name: { type: String, required: true }, email: { type: String, required: true } }); const User = createTimestampedModel<IUser>('User', userSchema);

最佳实践

  1. 使用接口定义类型:为所有 Schema 定义接口
  2. 严格类型检查:启用 TypeScript 的严格模式
  3. 避免 any 类型:尽量使用具体类型
  4. 使用类型断言:在必要时使用类型断言
  5. 文档完善:为类型添加清晰的注释
  6. 测试覆盖:为类型化模型编写测试
  7. 使用工具:利用 TypeScript 的类型提示和自动完成
标签:Mongoose