Vyhledávání na webu

typeguards - Proč TypeScript type guard nezúžuje unii?


V následujícím TypeScript kódu očekávám, že contact bude zúžen na GroupContact díky použití typu strážce, ale contact je stále Contact | GroupContact. Proč?

type Contact = {
    id: string;
    name: string;
    contactInfo: {
        isStarred: boolean;
    }
}

type GroupContact = Omit<Contact, "contactInfo">;

function isGroupContact(contact: Contact | GroupContact): contact is GroupContact {
    return !("contactInfo" in contact);
}

function foo(contact: Contact | GroupContact) {
    if (isGroupContact(contact)) {
        console.log("group", contact); // why contact is `Contact | GroupContact` here and not `GroupContact`?
    } else {
        console.log("not group", contact);
    }
}

Odpověď č. 1

Protože TypeScript používá strukturální typování. Zvažte tento příklad z příručky:

let o = { x: "hi", extra: 1 }; // ok
let o2: { x: string } = o; // ok

Zde má objektový literál { x: "hi", extra: 1 } odpovídající literální typ { x: string, extra: number }. Tento typ je přiřaditelný k { x: string }, protože má všechny požadované vlastnosti a tyto vlastnosti mají přiřaditelné typy. Přebytečná vlastnost při přiřazení nebrání, jen z něj činí podtyp { x: string }.

Kompilátor pracuje se kódem, který jste ukázal, stejným způsobem jako v příkladu: odvozený typ GroupContact vypadá po rozšíření takto:

TS hřiště

type GroupContact = Omit<Contact, "contactInfo">;
/*   ^?
type GroupContact = {
  id: string;
  name: string;
}
*/

Typ GroupContact neomezuje přítomnost vlastnosti contactInfo — znamená to pouze, že typ musí mít vlastnosti id a name typu string — což znamená, že jakákoli hodnota typu Contact je také přiřaditelná k GroupContact, takže typová ochrana ve skutečnosti nezúží typ.

Chcete-li naznačit, že typ „nesmí mít definovanou hodnotu u vlastnosti contactInfo“, můžete použít volitelnou vlastnost typu never:

type GroupContact = Omit<Contact, "contactInfo"> & { contactInfo?: never };

…a zúžení bude fungovat podle očekávání:

function foo(contact: Contact | GroupContact) {
  if (isGroupContact(contact)) {
    console.log("group", contact);
    //                   ^? (parameter) contact: GroupContact
  } else {
    console.log("not group", contact);
    //                       ^? (parameter) contact: Contact
  }
}

Kód v TS Playground


Viz také: exactOptionalPropertyTypes