blog
blog copied to clipboard
在 TypeScript 中定义 Mongoose 的 Schema、Model 与 Document
定义静态方法与实例方法
mongoose 可以在 Schema 上定义 Model 的静态方法与 Document 的实例方法,但 TypeScript 是无法识别到的。
例如下面的代码:
const { Schema, model } = require('mongoose')
const userSchema = new Schema({
name: String,
pwd: String
})
// 定义 Model 的静态方法
userSchema.statics.findUser = function(name, pwd) {
return this.findOne({ name, pwd }).exec()
}
// 定义 Document 的实例方法
userSchema.methods.sayHi = function() {
console.log(`My name is ${this.name} and my password is ${this.pwd}.`)
}
// 现在就可以使用上面定义的两个方法了
const UserModel = model('User', userSchema)
// 使用静态方法
UserModel.findUser('limingkai', '123').then(...)
const newUser = new UserModel({
name: 'hh',
pwd: '456'
})
// 使用实例方法
newUser.sayHi()
我在 Schema 上定义了一个静态方法和一个实例方法,但如果用 TypeScript 写上面的代码,在调用这两个方法的时候 VS Code 会报错,提示我找不到这两个方法的类型定义。
为了让 TypeScript 能检测到这些方法,只能自己手动将这些方法的类型添加上去了,例如上面的代码在 TypeScript 中可以这样写:
import { Document, Schema, Model, model } from 'mongoose'
interface UserDocument extends Document {
// 属性的结构等同于 Schema 中的定义
name: string
pwd: string
// 实例方法在这里定义
sayHi(this: UserDocument): void
}
// 定义 UserModel
interface UserModelConstructor extends Model<UserDocument> {
// 静态方法在这里定义
findUser(this: Model<UserDocument>): Promise<UserDocument>
}
const userSchema = new Schema({
name: String,
pwd: String
})
// 注册静态方法
userSchema.statics.findUser = function(name, pwd) {
// 这里能识别 this 的类型
return this.findOne({ name, pwd }).exec()
} as UserModelConstructor['findUser']
// 注册实例方法
userSchema.methods.sayHi = function() {
// 这里也能识别 this 的类型
console.log(`My name is ${this.name} and my password is ${this.pwd}.`)
} as UserDocument['sayHi']
const UserModel = model('User', userSchema) as UserModelConstructor
// 可以识别到 UserModel.findUser 了
UserModel.findUser(...)
const newUser = new UserModel({ name: 'hh', pwd: '456' })
// 也能识别到实例上的 sayHi() 方法了
newUser.sayHi()
定义非 Document 实例类型
mongoose 的 .lean() 方法返回的是一个不是 Document 实例的纯数据,为了与 Document 区分,一开始我的做法是定义两个结构差不多的类型,例如:
interface RawUser {
_id: ObjectID
name: string
age: number
}
interface User extends Document {
_id: ObjectID
name: string
age: number
}
上面的例子中,RawUser 与 User 之间唯一的不同就是 User 继承了 Document 而 RawUser 没有,但维护这样两个 interface 很繁琐且容易出错,后来我找到了这样一种写法:
interface RawUser {
_id: ObjectID
name: string
age: number
}
type RawUserDoc = RawUser & Document
interface UserDoc extends RawUserDoc {
// Document 上的 _id 的类型是 any,
// 这里为了覆盖它的类型所以用了一个新的 interface
_id: ObjectID
}
社区提供的解决方案
最近发现社区里已经有人针对这个问题提供了一种解决方案:https://github.com/szokodiakos/typegoose