blog icon indicating copy to clipboard operation
blog copied to clipboard

在 TypeScript 中定义 Mongoose 的 Schema、Model 与 Document

Open lmk123 opened this issue 7 years ago • 0 comments

定义静态方法与实例方法

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
}

上面的例子中,RawUserUser 之间唯一的不同就是 User 继承了 DocumentRawUser 没有,但维护这样两个 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

lmk123 avatar Apr 11 '18 11:04 lmk123