TypeScript 中的模式
Mongoose 模式 是你告诉 Mongoose 你的文档结构的方式。Mongoose 模式与 TypeScript 接口是分开的,因此你需要定义一个原始文档接口和一个模式;或者依赖 Mongoose 自动从模式定义中推断类型。
自动类型推断
Mongoose 可以根据你的模式定义自动推断文档类型,如下所示。我们建议在定义模式和模型时依赖自动类型推断。
import { Schema, model } from 'mongoose';
// Schema
const schema = new Schema({
name: { type: String, required: true },
email: { type: String, required: true },
avatar: String
});
// `UserModel` will have `name: string`, etc.
const UserModel = mongoose.model('User', schema);
const doc = new UserModel({ name: 'test', email: 'test' });
doc.name; // string
doc.email; // string
doc.avatar; // string | undefined | null
使用自动类型推断有一些注意事项
- 你需要在
tsconfig.json
中设置strictNullChecks: true
或strict: true
。或者,如果你在命令行中设置标志,则为--strictNullChecks
或--strict
。在禁用严格模式的情况下,自动类型推断存在 已知问题。 - 你需要在
new Schema()
调用中定义你的模式。不要将你的模式定义分配给一个临时变量。执行类似const schemaDefinition = { name: String }; const schema = new Schema(schemaDefinition);
的操作将不起作用。 - Mongoose 会将
createdAt
和updatedAt
添加到你的模式中,前提是你已经在模式中指定了timestamps
选项,除非你也指定了methods
、virtuals
或statics
。在使用时间戳和 methods/virtuals/statics 选项时,类型推断存在 已知问题。如果你使用 methods、virtuals 和 statics,则需要负责将createdAt
和updatedAt
添加到你的模式定义中。
如果你必须单独定义模式,请使用 as const (const schemaDefinition = { ... } as const;
) 来防止类型扩展。TypeScript 会自动将 required: false
等类型扩展为 required: boolean
,这会导致 Mongoose 假设该字段是必需的。使用 as const
会强制 TypeScript 保留这些类型。
如果你需要从模式定义中显式获取原始文档类型(从 doc.toObject()
、await Model.findOne().lean()
等返回的值),可以使用 Mongoose 的 inferRawDocType
帮助程序,如下所示
import { Schema, InferRawDocType, model } from 'mongoose';
const schemaDefinition = {
name: { type: String, required: true },
email: { type: String, required: true },
avatar: String
} as const;
const schema = new Schema(schemaDefinition);
const UserModel = model('User', schema);
const doc = new UserModel({ name: 'test', email: 'test' });
type RawUserDocument = InferRawDocType<typeof schemaDefinition>;
useRawDoc(doc.toObject());
function useRawDoc(doc: RawUserDocument) {
// ...
}
如果自动类型推断不适合你,你始终可以回退到文档接口定义。
单独的文档接口定义
如果自动类型推断不适合你,你可以定义一个单独的原始文档接口,如下所示。
import { Schema } from 'mongoose';
// Raw document interface. Contains the data type as it will be stored
// in MongoDB. So you can ObjectId, Buffer, and other custom primitive data types.
// But no Mongoose document arrays or subdocuments.
interface User {
name: string;
email: string;
avatar?: string;
}
// Schema
const schema = new Schema<User>({
name: { type: String, required: true },
email: { type: String, required: true },
avatar: String
});
默认情况下,Mongoose 不会检查你的原始文档接口是否与你的模式一致。例如,上面的代码如果在文档接口中 email
是可选的,但在 schema
中是 required
的,不会抛出错误。
泛型参数
TypeScript 中的 Mongoose Schema
类有 9 个 泛型参数
RawDocType
- 描述数据如何在 MongoDB 中保存的接口TModelType
- Mongoose 模型类型。如果没有要定义的查询助手或实例方法,则可以省略。- 默认值:
Model<DocType, any, any>
- 默认值:
TInstanceMethods
- 包含模式方法的接口。- 默认值:
{}
- 默认值:
TQueryHelpers
- 包含在模式上定义的查询助手的接口。默认值为{}
。TVirtuals
- 包含在模式上定义的虚拟属性的接口。默认值为{}
TStaticMethods
- 包含模型上方法的接口。默认值为{}
TSchemaOptions
- 传递给Schema()
构造函数的第二个选项的类型。默认值为DefaultSchemaOptions
。DocType
- 从模式中推断的文档类型。THydratedDocumentType
- 已水化的文档类型。这是await Model.findOne()
、Model.hydrate()
等的默认返回值类型。
查看 TypeScript 定义
export class Schema<
RawDocType = any,
TModelType = Model<RawDocType, any, any, any>,
TInstanceMethods = {},
TQueryHelpers = {},
TVirtuals = {},
TStaticMethods = {},
TSchemaOptions = DefaultSchemaOptions,
DocType = ...,
THydratedDocumentType = HydratedDocument<FlatRecord<DocType>, TVirtuals & TInstanceMethods>
>
extends events.EventEmitter {
// ...
}
第一个泛型参数 DocType
表示 Mongoose 将存储在 MongoDB 中的文档类型。Mongoose 会将 DocType
包装在一个 Mongoose 文档中,用于诸如文档中间件的 this
参数等情况。例如
schema.pre('save', function(): void {
console.log(this.name); // TypeScript knows that `this` is a `mongoose.Document & User` by default
});
第二个泛型参数 M
是与模式一起使用的模型。Mongoose 在模式中定义的模型中间件中使用 M
类型。
第三个泛型参数 TInstanceMethods
用于添加在模式中定义的实例方法的类型。
第四个参数 TQueryHelpers
用于添加 链式查询助手 的类型。
模式与接口字段
Mongoose 会检查以确保模式中的每个路径都在文档接口中定义。
例如,以下代码将无法编译,因为 email
是模式中的一个路径,但在 DocType
接口中没有。
import { Schema, Model } from 'mongoose';
interface User {
name: string;
email: string;
avatar?: string;
}
// Object literal may only specify known properties, but 'emaill' does not exist in type ...
// Did you mean to write 'email'?
const schema = new Schema<User>({
name: { type: String, required: true },
emaill: { type: String, required: true },
avatar: String
});
但是,Mongoose 不会检查文档接口中存在但在模式中不存在的路径。例如,以下代码可以编译。
import { Schema, Model } from 'mongoose';
interface User {
name: string;
email: string;
avatar?: string;
createdAt: number;
}
const schema = new Schema<User, Model<User>>({
name: { type: String, required: true },
email: { type: String, required: true },
avatar: String
});
这是因为 Mongoose 有许多功能可以将路径添加到你的模式中,这些路径应该包含在 DocType
接口中,而无需你显式地将这些路径放在 Schema()
构造函数中。例如,时间戳 和 插件。
数组
当你在文档接口中定义数组时,我们建议使用普通 JavaScript 数组,不要使用 Mongoose 的 Types.Array
类型或 Types.DocumentArray
类型。相反,使用 THydratedDocumentType
泛型来定义模型和模式,以指示已水化的文档类型具有 Types.Array
和 Types.DocumentArray
类型的路径。
import mongoose from 'mongoose'
const { Schema } = mongoose;
interface IOrder {
tags: Array<{ name: string }>
}
// Define a HydratedDocumentType that describes what type Mongoose should use
// for fully hydrated docs returned from `findOne()`, etc.
type OrderHydratedDocument = mongoose.HydratedDocument<
IOrder,
{ tags: mongoose.HydratedArraySubdocument<{ name: string }> }
>;
type OrderModelType = mongoose.Model<
IOrder,
{},
{},
{},
OrderHydratedDocument // THydratedDocumentType
>;
const orderSchema = new mongoose.Schema<
IOrder,
OrderModelType,
{}, // methods
{}, // query helpers
{}, // virtuals
{}, // statics
mongoose.DefaultSchemaOptions, // schema options
IOrder, // doctype
OrderHydratedDocument // THydratedDocumentType
>({
tags: [{ name: { type: String, required: true } }]
});
const OrderModel = mongoose.model<IOrder, OrderModelType>('Order', orderSchema);
// Demonstrating return types from OrderModel
const doc = new OrderModel({ tags: [{ name: 'test' }] });
doc.tags; // mongoose.Types.DocumentArray<{ name: string }>
doc.toObject().tags; // Array<{ name: string }>
async function run() {
const docFromDb = await OrderModel.findOne().orFail();
docFromDb.tags; // mongoose.Types.DocumentArray<{ name: string }>
const leanDoc = await OrderModel.findOne().orFail().lean();
leanDoc.tags; // Array<{ name: string }>
};
对于数组子文档类型,使用 HydratedArraySubdocument<RawDocType>
,对于单个子文档,使用 HydratedSingleSubdocument<RawDocType>
。
如果你没有使用 模式方法、中间件或 虚拟属性,则可以省略 Schema()
的最后 7 个泛型参数,只需使用 new mongoose.Schema<IOrder, OrderModelType>(...)
定义你的模式。模式的 THydratedDocumentType 参数主要用于设置方法和虚拟属性的 this
的值。