TypeScript 中的 Schema
Mongoose schema 定義了 Mongoose 如何看待您的文件。Mongoose schema 與 TypeScript 介面是分開的,因此您需要定義一個原始文件介面和一個schema;或者依靠 Mongoose 從 schema 定義自動推斷型別。
自動型別推斷
Mongoose 可以從您的 schema 定義中自動推斷文件型別,如下所示。我們建議在定義 schema 和模型時依賴自動型別推斷。
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()
呼叫中定義您的 schema。不要將您的 schema 定義指派給臨時變數。類似const schemaDefinition = { name: String }; const schema = new Schema(schemaDefinition);
的做法將不起作用。 - 如果您在 schema 中指定
timestamps
選項,Mongoose 會將createdAt
和updatedAt
新增到您的 schema 中,除非您也指定methods
、virtuals
或statics
。在時間戳記與方法/虛擬/靜態選項的型別推斷方面,存在已知問題。如果您使用方法、虛擬和靜態,您需要負責將createdAt
和updatedAt
新增到您的 schema 定義中。
如果您必須單獨定義您的 schema,請使用 as const(const schemaDefinition = { ... } as const;
)來防止型別擴展。TypeScript 會自動將類似 required: false
的型別擴展為 required: boolean
,這會導致 Mongoose 假設該欄位是必填的。使用 as const
會強制 TypeScript 保留這些型別。
如果您需要從您的 schema 定義中明確取得原始文件型別(從 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 不會檢查您的原始文件介面是否與您的 schema 一致。例如,如果 email
在文件介面中是選填的,但在 schema
中是 required
,則上述程式碼不會擲回錯誤。
泛型參數
TypeScript 中的 Mongoose Schema
類別有 9 個 泛型參數
RawDocType
- 一個描述資料如何在 MongoDB 中儲存的介面TModelType
- Mongoose 模型型別。如果沒有要定義的查詢輔助函數或實例方法,則可以省略。- 預設值:
Model<DocType, any, any>
- 預設值:
TInstanceMethods
- 一個包含 schema 方法的介面。- 預設值:
{}
- 預設值:
TQueryHelpers
- 一個包含在 schema 上定義的查詢輔助函數的介面。預設為{}
。TVirtuals
- 一個包含在 schema 上定義的虛擬屬性的介面。預設為{}
TStaticMethods
- 一個包含模型方法的介面。預設為{}
TSchemaOptions
- 作為第二個選項傳遞給Schema()
建構函式的型別。預設為DefaultSchemaOptions
。DocType
- 從 schema 推斷的文件型別。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 中儲存的文件型別。對於類似於文件中介軟體的 this
參數的情況,Mongoose 會將 DocType
包裝在 Mongoose 文件中。例如
schema.pre('save', function(): void {
console.log(this.name); // TypeScript knows that `this` is a `mongoose.Document & User` by default
});
第二個泛型參數 M
是與 schema 一起使用的模型。Mongoose 在 schema 中定義的模型中介軟體中使用 M
型別。
第三個泛型參數 TInstanceMethods
用於新增 schema 中定義的實例方法型別。
第四個參數 TQueryHelpers
用於新增 可鏈接的查詢輔助函數型別。
Schema 與介面欄位
Mongoose 會檢查您 schema 中的每個路徑是否都在您的文件介面中定義。
例如,下面的程式碼將無法編譯,因為 email
是 schema 中的路徑,但不是 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 不會檢查存在於文件介面中但不存在於 schema 中的路徑。例如,下面的程式碼會編譯。
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 有許多功能可以將路徑新增到您的 schema 中,這些路徑應該包含在 DocType
介面中,而無需您在 Schema()
建構函式中明確放置這些路徑。例如,時間戳記和外掛。
陣列
當您在文件介面中定義陣列時,我們建議使用原始 JavaScript 陣列,而不是 Mongoose 的 Types.Array
型別或 Types.DocumentArray
型別。而是使用模型和 schema 的 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 方法、中介軟體或虛擬屬性,您可以省略 Schema()
的最後 7 個泛型參數,只需使用 new mongoose.Schema<IOrder, OrderModelType>(...)
來定義您的 schema。Schema 的 THydratedDocumentType 參數主要用於設定方法和虛擬屬性中的 this
的值。