"Objektet kan vara 'undefined'." i TypeScript


Koden nedan kastar Object is possibly 'undefined'. vid obj.field1 += ',' + v;. TypeScript säger att obj kan vara undefined, men obj kan inte vara undefined vid denna punkt eftersom {field1: 'testtest'} tilldelas om map.get(key) returnerar undefined.

Varför fick jag detta fel? Hur kan jag åtgärda det?

interface TestIF {
  field1: string;
}

export class MyClass {
  test(): void {
    const map1: Map<string, TestIF> = new Map();
    const map2: Map<string, string> = new Map();

    const key = 'mapkey';
    let obj = map1.get(key);
    if (obj == null) {
      obj = {field1: 'testtest'};
      map1.set(key, obj);
    }

    map2.forEach( v => {
      obj.field1 += ',' + v;  // Object is possibly 'undefined'.
    });
  }
}

Svar #1

Felet uppstår eftersom kontrollflödesanalys är svår (se microsoft/TypeScript#9998), särskilt när den information du vill hålla reda på inte kan representeras i typesystemet.

I det allmänna fallet kan kompilatorn verkligen inte lista ut mycket om vad som händer när funktioner accepterar callback-funktioner som ändrar variabler. Kontrollflödet inuti sådana funktioner är ett fullständigt mysterium; kanske kommer callbacken att anropas omedelbart och exakt en gång. Kanske kommer callbacken aldrig att anropas. Kanske kommer callbacken att anropas en miljon gånger. Eller kanske kommer den att anropas asynkront, långt fram i tiden. Eftersom det allmänna fallet är så hopplöst, försöker kompilatorn inte ens riktigt. Den använder några heuristiker som fungerar för många fall, och som också nödvändigtvis misslyckas för många fall. Du valde ett av misslyckandena.

Heuristiken som används här är att inne i en callback återställs alla inskränkningar som inträffade i den bredare omfattningen. Det gör rimliga saker för kod som denna:

// who knows when this actually calls its callback?
declare function mysteryCallbackCaller(cb: () => void): void;

let a: string | undefined = "hey";
mysteryCallbackCaller(() => a.charAt(0)); // error!  a may be undefined
a = undefined;

Kompilatorn vet inte när eller om () => a.charAt(0) kommer att anropas. Om det anropas omedelbart när mysteryCallbackCaller() kallas, kommer a att vara definierad. Men om det anropas någon gång senare kan a vara odefinierad. Eftersom kompilatorn inte kan garantera säkerhet här, rapporterar den ett fel.


Så vad kan vi göra för att ta itu med detta problem i ditt fall? Det finns två huvudsakliga lösningar jag kan tänka på. En är att helt enkelt berätta för kompilatorn att den har fel och att du är säker på att obj kommer att vara definierad. Detta kan göras med hjälp av ! icke-null-assertionsoperatorn:

map2.forEach(v => {
  obj!.field1 += "," + v; // okay now
});

Detta fungerar utan kompileringstidfel. Nackdelen med denna lösning är att ansvaret för att säkerställa att obj är definierad nu enbart ligger på dig och inte på kompilatorn. Om du ändrar den föregående koden och obj verkligen möjligen är odefinierad, kommer typangivelsen fortfarande att undertrycka felet, och du kommer att få problem vid körning.


Den andra lösningen är att ändra det du gör så att kompilatorn kan verifiera att din callback är säker. Det enklaste sättet att göra det är att använda en ny variabel:

// over here the compiler knows obj is defined
const constObj = obj; // type is inferred as TestIF
map2.forEach(v => {
  constObj.field1 += "," + v; // okay, constObj is TestIF, so this works
});

Allt jag har gjort här är att tilldela obj till constObj. Men vid tidpunkten för denna tilldelning kan obj inte vara undefined. Därför är constObj bara ett TestIF, och inte ett TestIF | undefined. Och eftersom constObj aldrig omtilldelas och inte kan vara undefined, fungerar resten av koden.


Länk till kod

Relaterade artiklar