node.js - TypeScript - Ako definovať model v kombinácii s používaním mongoose populate?


Pozadie

Používam mongoose a TypeScript vo svojej Node.JS aplikácii. Používam mongoose populate na viacerých miestach pri získavaní údajov z databázy.

Problém, ktorému čelím, je, že neviem, ako typovať moje modely tak, aby vlastnosť mohla byť buď ObjectId, alebo naplnená dátami z inej kolekcie.

Čo som skúsil

Skúsil som použiť typy zjednotenia vo svojej definícii typu modelu, čo vyzerá ako niečo, čo TypeScript ponúka na riešenie takýchto vecí:

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

interface Item extends Document {
    _id: Types.ObjectId;

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

Môj schéma definuje vlastnosť iba ako ObjectId s odkazom.

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

Príklad:

Takže by som mohol urobiť niečo takéto:

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

Čo spôsobuje kompiláciu chyby:

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

Otázka

Ako môžem mať vlastnosť modelu, ktorá môže byť v TypeScripte jedného z dvoch typov?

Odpoveď č. 1

Musíte použiť typový strážca (type guard), aby ste zúžili typ z Types.ObjectId | User na User...

Ak pracujete s triedou User, môžete použiť toto:

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

Ak máte štruktúru, ktorá zodpovedá User, ale nie je inštanciou triedy (napríklad ak je User rozhraním), budete potrebovať vlastnú ochranu typu:

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

Ktorý môžete použiť s:

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

Ak nechcete pre tento účel kontrolovať štruktúry, môžete použiť diskriminovanú zjednotenie.

Odpoveď č. 2

Preveďte item.user na User, keď je používateľ vyplnený.

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

Súvisiace články