使用 TypeScript 填充

¥Populate with TypeScript

Mongoose 的 TypeScript 绑定populate() 添加通用参数 Paths

¥Mongoose's TypeScript bindings add a generic parameter Paths to the populate():

import { Schema, model, Document, Types } from 'mongoose';

// `Parent` represents the object as it is stored in MongoDB
interface Parent {
  child?: Types.ObjectId,
  name?: string
}
const ParentModel = model<Parent>('Parent', new Schema({
  child: { type: Schema.Types.ObjectId, ref: 'Child' },
  name: String
}));

interface Child {
  name: string;
}
const childSchema: Schema = new Schema({ name: String });
const ChildModel = model<Child>('Child', childSchema);

// Populate with `Paths` generic `{ child: Child }` to override `child` path
ParentModel.findOne({}).populate<{ child: Child }>('child').orFail().then(doc => {
  // Works
  const t: string = doc.child.name;
});

另一种方法是定义 PopulatedParent 接口并使用 Pick<> 来提取你正在填充的属性。

¥An alternative approach is to define a PopulatedParent interface and use Pick<> to pull the properties you're populating.

import { Schema, model, Document, Types } from 'mongoose';

// `Parent` represents the object as it is stored in MongoDB
interface Parent {
  child?: Types.ObjectId,
  name?: string
}
interface Child {
  name: string;
}
interface PopulatedParent {
  child: Child | null;
}
const ParentModel = model<Parent>('Parent', new Schema({
  child: { type: Schema.Types.ObjectId, ref: 'Child' },
  name: String
}));
const childSchema: Schema = new Schema({ name: String });
const ChildModel = model<Child>('Child', childSchema);

// Populate with `Paths` generic `{ child: Child }` to override `child` path
ParentModel.findOne({}).populate<Pick<PopulatedParent, 'child'>>('child').orFail().then(doc => {
  // Works
  const t: string = doc.child.name;
});

使用 PopulatedDoc

¥Using PopulatedDoc

Mongoose 还导出 PopulatedDoc 类型,帮助你在文档界面中定义填充文档:

¥Mongoose also exports a PopulatedDoc type that helps you define populated documents in your document interface:

import { Schema, model, Document, PopulatedDoc } from 'mongoose';

// `child` is either an ObjectId or a populated document
interface Parent {
  child?: PopulatedDoc<Document<ObjectId> & Child>,
  name?: string
}
const ParentModel = model<Parent>('Parent', new Schema({
  child: { type: 'ObjectId', ref: 'Child' },
  name: String
}));

interface Child {
  name?: string;
}
const childSchema: Schema = new Schema({ name: String });
const ChildModel = model<Child>('Child', childSchema);

ParentModel.findOne({}).populate('child').orFail().then((doc: Parent) => {
  const child = doc.child;
  if (child == null || child instanceof ObjectId) {
    throw new Error('should be populated');
  } else {
    // Works
    doc.child.name.trim();
  }
});

但是,我们建议使用第一部分中的 .populate<{ child: Child }> 语法而不是 PopulatedDoc。原因有两个:

¥However, we recommend using the .populate<{ child: Child }> syntax from the first section instead of PopulatedDoc. Here's two reasons why:

  1. 你仍然需要添加额外的检查来检查 child instanceof ObjectId。否则,TypeScript 编译器将失败并显示 Property name does not exist on type ObjectId。因此,使用 PopulatedDoc<> 意味着你在使用 doc.child 的任何地方都需要进行额外检查。

    ¥You still need to add an extra check to check if child instanceof ObjectId. Otherwise, the TypeScript compiler will fail with Property name does not exist on type ObjectId. So using PopulatedDoc<> means you need an extra check everywhere you use doc.child.

  2. Parent 接口中,child 是一个水合文档,这使得当你使用 lean()toObject() 时,Mongoose 很难推断出 child 的类型。

    ¥In the Parent interface, child is a hydrated document, which makes it slow difficult for Mongoose to infer the type of child when you use lean() or toObject().