【问题标题】:TypeScript - How to define model in combination with using mongoose populate?TypeScript - 如何结合使用猫鼬填充来定义模型?
【发布时间】:2018-06-04 00:56:03
【问题描述】:

背景

我在我的 Node.JS 应用程序中使用猫鼬和 TypeScript。从数据库中获取数据时,我在很多地方都使用了猫鼬的populate

我面临的问题是我不知道如何键入我的模型,以便属性可以是 ObjectId 或填充来自另一个集合的数据。

我尝试过的

我尝试在我的模型类型定义中使用联合类型,这似乎是 TypeScript 提供的用于涵盖这些类型的东西:

interface User extends Document {
    _id: Types.ObjectId;
    name: string
}

interface Item extends Document {
    _id: Types.ObjectId;

    // Union typing here
    user: Types.ObjectId | User;
}

我的架构仅将属性定义为带有 ref 的 ObjectId。

const ItemSchema = new Schema({
    user: { type: Schema.Types.ObjectId, ref: "User", index: true }
})

示例

所以我可能会这样做:

ItemModel.findById(id).populate("user").then((item: Item) => {
    console.log(item.user.name);
})

产生编译错误:

[ts] Property 'name' does not exist on type 'User | ObjectId'.
     Property 'name' does not exist on type 'ObjectId'.

问题

如何在 TypeScript 中拥有可以是两种类型之一的模型属性?

【问题讨论】:

    标签: node.js typescript mongoose


    【解决方案1】:

    您需要使用类型保护将类型从Types.ObjectId | User 缩小到User...

    如果你正在处理一个User 类,你可以使用这个:

    if (item.user instanceof User) {
        console.log(item.user.name);
    } else {
        // Otherwise, it is a Types.ObjectId
    }
    

    如果您的结构与 User 匹配,但不是类的实例(例如,如果 User 是接口),则需要自定义类型保护:

    function isUser(obj: User | any) : obj is User {
        return (obj && obj.name && typeof obj.name === 'string');
    }
    

    你可以使用的:

    if (isUser(item.user)) {
        console.log(item.user.name);
    } else {
        // Otherwise, it is a Types.ObjectId
    }
    

    如果您不想为此检查结构,可以使用discriminated union

    【讨论】:

    • 我确实读过类型保护并尝试应用它们,但我的 tsc 为它们输出错误。它说[ts] 'User' only refers to a type, but is being used as a value here. 代表行if (item.user instanceof User) { ... }
    • @maxpaj instanceof 需要一个具体的类型——它不适用于接口。为此需要使用自定义类型保护的第二个选项。
    • 这对我来说效果很好,并给了我阅读一般类型保护的线索。谢谢!
    • 抱歉,它不起作用。正如 maxpaj 所说,您有一个错误“用户”仅指一种类型......并且使用 typeof obj === 'Interface' 它总是会返回 false
    【解决方案2】:

    当用户被填充时,将item.user 转换为User

    ItemModel.findById(id).populate("user").then((item: Item) => {
        console.log((<User>item.user).name);
    })
    

    【讨论】:

      【解决方案3】:

      您可以使用 @types/mongoose 库中的 PopulatedDoc 类型。见mongoose.doc

      【讨论】:

      • 它返回 any 作为类型,而不是期望的行为
      【解决方案4】:

      Mongoose 的 TypeScript 绑定导出一个 PopulatedDoc 类型,可帮助您在 TypeScript 定义中定义填充文档:

      import { Schema, model, Document, PopulatedDoc } from 'mongoose';
      
      // `child` is either an ObjectId or a populated document
      interface Parent {
        child?: PopulatedDoc<Child & Document>,
        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) => {
        // Works
        doc.child.name.trim();
      })
      

      下面是 PopulatedDoc 类型的简化实现。它采用 2 个通用参数:填充的文档类型 PopulatedType 和未填充的类型 RawId。 RawId 默认为 ObjectId。

      type PopulatedDoc<PopulatedType, RawId = Types.ObjectId> = PopulatedType | RawId;
      

      作为开发人员,您有责任在填充文档和未填充文档之间强制执行强类型化。下面是一个例子。

      ParentModel.findOne({}).populate('child').orFail().then((doc: Parent) => {
        // `doc` doesn't have type information that `child` is populated
        useChildDoc(doc.child);
      });
      
      // You can use a function signature to make type checking more strict.
      function useChildDoc(child: Child): void {
        console.log(child.name.trim());
      }
      

      这是从文档中提取的。你可以查看here

      【讨论】:

      • 如果您使用populate(...).then,则此方法有效,但如果您使用 await 获取结果,则此方法无效。
      猜你喜欢
      • 2021-06-29
      • 2021-05-16
      • 2017-06-27
      • 2017-10-11
      • 2017-01-23
      • 2020-11-02
      • 2016-04-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多