typescript - Använda inbyggda typvaktsfunktioner för att förfina `unknown`/`object`-värde till en specifik objekttyp


Här är ett naivt försök:

const x: unknown = {a: 1};

if (
    typeof x === 'object' && 
    x !== null && 
    'a' in x && 
    typeof x.a === 'number' // error: Property 'a' does not exist on type 'object'.
) {
    console.log("x's type is a subtype of {a: number}");
}

Finns det något sätt att använda inbyggda typvakter för att särskilja här?

Svar #1

För närvarande fungerar insnävning av en objekttyp via in-operatorn som k in o endast när o är av en unionstyp, och resultatet blir att unionstypen insnäs till endast de medlemmar som har en känd k-nyckel. Detta implementerades i microsoft/TypeScript#15256. Men i ditt fall, när du kontrollerar "a" in x, är den uppenbara typen för x bara object-typen, som inte är en union och inte har en känd "a"-nyckel. Så in-operatorkontrollen gör inget användbart för dig.

Det finns en öppen funktionsförfrågan på microsoft/TypeScript#21732 som ber om att in också ska begränsa icke-union-inmatningar genom att bekräfta att den kontrollerade egenskapen finns. Så något i stil med "a" in x bör begränsa x från object till {a: unknown}. Det är oklart när eller om detta kommer att implementeras i TypeScript. För tillfället finns det lösningar.

Till exempel kan du skapa din egen användardefinierade typvakt-funktion som beter sig som du vill att in ska bete sig, och sedan kalla på den istället för in. Ett sätt att göra detta kan vara:

function inOperator<K extends string, T extends object>(
  k: K, o: T
): o is T & Record<K, unknown> {
  return k in o;
}

Och observera att det uppfyller ditt användningsfall:

const x: unknown = { a: 1 };

if (
  typeof x === 'object' &&
  x !== null &&
  inOperator('a', x) &&
  typeof x.a === 'number' // okay
) {
  x.a.toFixed(); // okay
  console.log("x's type is a subtype of {a: number}");
}

När x.a.toFixed() anropas har x.a framgångsrikt smalnats till number.

Playground-länk till kod