in guard în TypeScript

Narrowing și type guards TypeScript 2.7

"cheie" in x verifică dacă obiectul are proprietatea și restrânge union-ul la variantele care o au.

Sintaxă
if ("proprietate" in x) { /* x are proprietatea */ }

Ce face in guard

Operatorul in din JavaScript verifică dacă un obiect are o proprietate (proprie sau moștenită). Când x este un union de obiecte, "zboara" in x restrânge tipul la variantele care au proprietatea zboara, iar în else rămân celelalte.

Este potrivit pentru union-uri de obiecte care nu au o proprietate comună de tip literal (pentru acestea, discriminated unions sunt mai clare). Din TypeScript 4.9, in funcționează și pe valori object sau unknown deja verificate: după "nume" in x, tipul devine object & Record<"nume", unknown>.

Atenție: o proprietate opțională (email?) poate apărea în ambele ramuri, iar in spune doar că proprietatea există, nu și ce tip are valoarea ei.

Exemple

Exemplul 1: Union de obiecte fără proprietate comună

interface Card {
    numar: string;
    expira: string;
}
interface Transfer {
    iban: string;
}
type Plata = Card | Transfer;

function descrie(p: Plata): string {
    if ("iban" in p) return `transfer în ${p.iban.slice(0, 4)}…${p.iban.slice(-4)}`;
    return `card ****${p.numar.slice(-4)}, expiră ${p.expira}`;
}

console.log(descrie({ numar: "4111111111111111", expira: "08/28" }));
console.log(descrie({ iban: "RO49AAAA1B31007593840000" }));

Rezultat

card ****1111, expiră 08/28
transfer în RO49…0000

Exemplul 2: Verificarea unui obiect unknown

function nume(x: unknown): string {
    if (typeof x === "object" && x !== null && "nume" in x && typeof x.nume === "string") {
        return x.nume;
    }
    return "(fără nume)";
}

console.log(nume({ nume: "Ana", oras: "Iași" }));
console.log(nume({ nume: 42 }));
console.log(nume("Ana"));

Rezultat

Ana
(fără nume)
(fără nume)

Exemplul 3: Proprietate care nu există în nicio variantă

După ce "iban" in p a eliminat Transfer, în else a rămas doar Card, care nu are iban.

interface Card {
    numar: string;
}
interface Transfer {
    iban: string;
}
function f(p: Card | Transfer) {
    if ("iban" in p) console.log(p.iban);
    else console.log(p.iban);
}
f({ numar: "1" });

Eroarea afișată de tsc

exemplu.mts(9,24): error TS2339: Property 'iban' does not exist on type 'Card'.

Greșeli frecvente

  • Folosești in pe o valoare care poate fi primitivă ("x" in "text"). La rulare aruncă TypeError; tsc te obligă să verifici întâi că este obiect.
  • Te bazezi pe in pentru proprietăți opționale: un Card cu iban? ar trece și el testul.

Comenzi înrudite

  • type guards: Verificări la rulare pe care TypeScript le înțelege și după care știe tipul exact al unei valori.
  • discriminated unions: Un union de obiecte cu o proprietate comună de tip literal; verificarea ei arată exact ce variantă ai.
  • instanceof guard: x instanceof Clasa verifică dacă obiectul a fost creat din clasă și restrânge tipul la acea clasă.
  • narrowing: TypeScript urmărește verificările din cod (if, switch, return) și restrânge tipul unei variabile în fiecare ramură.