Schemas in TypeScriptTypeScript中的架构

Mongoose schemas are how you tell Mongoose what your documents look like. Mongoose schemas are separate from TypeScript interfaces, so you need to either define both a document interface and a schema; or rely on Mongoose to automatically infer the type from the schema definition.Mongoose架构是告诉Mongoose您的文档是什么样子的。Mongoose模式与TypeScript接口是分开的,因此您需要同时定义文档接口架构;或者依靠Mongoose从模式定义中自动推断类型。

Separate document interface definition单独的文档接口定义

import { Schema } from 'mongoose';

// Document interface
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
});

By default, Mongoose does not check if your document interface lines up with your schema. 默认情况下,Mongoose不会检查您的文档接口是否与您的模式一致。For example, the above code won't throw an error if email is optional in the document interface, but required in schema.例如,如果email在文档界面中是可选的,但在schema中是required,那么上面的代码不会抛出错误。

Automatic type inference自动类型推断

Mongoose can also automatically infer the document type from your schema definition as follows.Mongoose还可以根据您的模式定义自动推断文档类型,如下所示。

import { Schema, InferSchemaType } from 'mongoose';

// Document interface文档接口
// No need to define TS interface any more.不再需要定义TS接口。
// interface User {
// name: string;
// email: string;
// avatar?: string;
// }

// Schema
const schema = new Schema({
name: { type: String, required: true },
email: { type: String, required: true },
avatar: String
});

type User = InferSchemaType<typeof schema>;
// InferSchemaType will determine the type as follows:InferSchemaType将按如下方式确定类型:
// type User = {
// name: string;
// email: string;
// avatar?: string;
// }

// `UserModel` will have `name: string`, etc.
const UserModel = mongoose.model('User', schema);

There are a few caveats for using automatic type inference:对于使用自动类型推断,有一些注意事项:

  1. You need to set strictNullChecks: true or strict: true in your tsconfig.json. 您需要在tsconfig.json中设置strictNullChecks:truestrict:trueOr, if you're setting flags at the command line, --strictNullChecks or --strict. 或者,如果要在命令行设置标志,请使用--strictNullChecks--strictThere are known issues with automatic type inference with strict mode disabled.在禁用严格模式的情况下,自动类型推理存在已知问题
  2. You need to define your schema in the new Schema() call. 您需要在new Schema()调用中定义您的模式。Don't assign your schema definition to a temporary variable. 不要将架构定义分配给临时变量。Doing something like const schemaDefinition = { name: String }; const schema = new Schema(schemaDefinition); will not work.执行类似const schemaDefinition = { name: String }; const schema = new Schema(schemaDefinition);不会起作用。
  3. Mongoose adds createdAt and updatedAt to your schema if you specify the timestamps option in your schema, except if you also specify methods, virtuals, or statics. 如果您在模式中指定了timestamps选项,Mongoose会将createdAtupdatedAt添加到您的模式中,除非您还指定了methodsvirtualsstaticsThere is a known issue with type inference with timestamps and methods/virtuals/statics options. 使用时间戳和方法/virtuals/statics选项进行类型推断存在已知问题If you use methods, virtuals, and statics, you're responsible for adding createdAt and updatedAt to your schema definition.如果您使用方法、虚拟和静态,则需要负责将createdAtupdatedAt添加到模式定义中。

If automatic type inference doesn't work for you, you can always fall back to document interface definitions.如果自动类型推理对您不起作用,那么您总是可以回到文档接口定义。

Generic parameters通用参数

The Mongoose Schema class in TypeScript has 4 generic parameters:TypeScript中的Mongoose Schema类有4个通用参数

  • DocType - An interface descibing how the data is saved in MongoDB描述如何在MongoDB中保存数据的界面
  • M - The Mongoose model type. Can be omitted if there are no query helpers or instance methods to be defined.Mongoose模型类型。如果没有要定义的查询帮助程序或实例方法,则可以省略。
    • default: 默认:Model<DocType, any, any>
  • TInstanceMethods - An interface containing the methods for the schema.包含架构的方法的接口。
    • default: {}
  • TQueryHelpers - An interface containing query helpers defined on the schema. 包含在架构上定义的查询帮助程序的接口。Defaults to {}.默认为{}
View TypeScript definition查看TypeScript定义
class Schema<DocType = any, M = Model<DocType, any, any>, TInstanceMethods = {}, TQueryHelpers = {}> extends events.EventEmitter {
// ...
}

The first generic param, DocType, represents the type of documents that Mongoose will store in MongoDB. 第一个通用参数DocType表示Mongoose将存储在MongoDB中的文档类型。Mongoose wraps DocType in a Mongoose document for cases like the this parameter to document middleware. 对于像this参数这样的情况,Mongoose将DocType封装在Mongoose文档中,以文档中间件。For example:例如:

schema.pre('save', function(): void {
console.log(this.name); // TypeScript knows that `this` is a `mongoose.Document & User` by default
});

The second generic param, M, is the model used with the schema. 第二个通用参数M是与模式一起使用的模型。Mongoose uses the M type in model middleware defined in the schema.Mongoose使用模式中定义的M类型的模型中间件。

The third generic param, TInstanceMethods is used to add types for instance methods defined in the schema.第三个通用参数TInstanceMethods用于为模式中定义的实例方法添加类型。

The 4th param, TQueryHelpers, is used to add types for chainable query helpers.第四个参数TQueryHelpers用于添加可链接查询帮助程序的类型。

Schema vs Interface fields架构与接口字段

Mongoose checks to make sure that every path in your schema is defined in your document interface.Mongoose会进行检查,以确保架构中的每个路径都在文档接口中定义。

For example, the below code will fail to compile because email is a path in the schema, but not in the DocType interface.例如,下面的代码将无法编译,因为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 ...对象文字只能指定已知的属性,但类型中不存在“emaill”……
// Did you mean to write 'email'?你是想写“电子邮件”吗?
const schema = new Schema<User>({
name: { type: String, required: true },
emaill: { type: String, required: true },
avatar: String
});

However, Mongoose does not check for paths that exist in the document interface, but not in the schema. 然而,Mongoose不会检查文档接口中存在的路径,但不会检查模式中存在的。For example, the below code compiles.例如,以下代码进行编译。

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
});

This is because Mongoose has numerous features that add paths to your schema that should be included in the DocType interface without you explicitly putting these paths in the Schema() constructor. 这是因为Mongoose有许多功能,可以向您的模式添加路径,这些路径应该包含在DocType接口中,而无需将这些路径显式地放入schema()构造函数中。For example, timestamps and plugins.例如,时间戳插件

Arrays数组

When you define an array in a document interface, we recommend using Mongoose's Types.Array type for primitive arrays or Types.DocumentArray for arrays of documents.当您在文档界面中定义数组时,我们建议将Mongoose的Types.Array类型用于基元数组,或将Types.DocumentArray用于文档数组。

import { Schema, Model, Types } from 'mongoose';

interface BlogPost {
_id: Types.ObjectId;
title: string;
}

interface User {
tags: Types.Array<string>;
blogPosts: Types.DocumentArray<BlogPost>;
}

const schema = new Schema<User, Model<User>>({
tags: [String],
blogPosts: [{ title: String }]
});

Using Types.DocumentArray is helpful when dealing with defaults. 在处理默认值时,使用Types.DocumentArray很有帮助。For example, BlogPost has an _id property that Mongoose will set by default. If you use Types.DocumentArray in the above case, you'll be able to push() a subdocument without an _id.例如,BlogPost有一个_id属性,Mongoose将默认设置该属性。如果在上述情况下使用Types.DocumentArray,则可以push()不带_id的子文档。

const user = new User({ blogPosts: [] });

user.blogPosts.push({ title: 'test' }); // Would not work if you did `blogPosts: BlogPost[]`