react redux - TypeScript tappar typkunskap inne i callback-funktion (t.ex. .filter och .some callback-funktioner)


Jag ser ett mycket konstigt TypeScript-beteende som jag inte förstår. Här är scenariot:

Jag har tre olika gränssnitt som beskriver en händelse. De ser ut så här

interface ReceiveMessageEvent {
  event: 'receive-message';
  payload: {
    action: 'receive-message';
    businessSMSId: number;
    message: {
      businessSMSId: number;
      from: string;
      to: string;
    };
  };
}

interface ReadMessageEvent {
  event: 'read-message';
  payload: {
    action: 'read-message';
    businessSMSId: number;
    customerNumber: string;
    inboxNumber: string;
  };
}

interface SetThreadsEvent {
  event: 'set-threads';
  threads: any[];
}

Som du kan se har alla tre gränssnitten en event-egenskap som är en specifik sträng, vilket innebär att det bara kan finnas tre möjliga alternativ, antingen 'receive-message', 'read-message' eller 'set-threads'. Detta är viktigt eftersom jag nästa har ett switch-uttryck där fallet är denna event-egenskap:

switch (action.payload.event) {
  case 'receive-message':
    // ...
  case 'read-message':
    // ...
  case 'set-threads':
    // ...
}

Så, eftersom fallet är händelseegenskapen, skulle jag förvänta mig att TypeScript känner till formen på resten av datan beroende på vilket fall vi är i. Nu, gör DOES detta korrekt (som jag skulle förvänta mig), förutom i ett specifikt scenario, vilket är där min förvirring kommer ifrån. Och det fallet är när jag är inne i callback-funktionen för .filter-metoden.

Så till exempel:

switch (action.payload.event) {
  case 'receive-message':
    // This is correct and has no errors
    const test = action.payload.payload.message.to

    // This is giving me a TS error, even though I'm accessing the exact same property
    // As in the line above
    myArray.filter((thread) => thread.property === action.payload.payload.message.to)
                                                                 ^^^^^^^^ TS error here
  case 'read-message':
    // ...
  case 'set-threads':
    // ...
}

Det exakta felet från exemplet ovan är detta:

Property 'payload' does not exist on type 'ReceiveMessageEvent | ReadMessageEvent | SetThreadsEvent'.
  Property 'payload' does not exist on type 'SetThreadsEvent'.

Det är som om Typescript tappar sin typkunskap när jag går in i callback-funktionen. Koden fungerar faktiskt exakt som förväntat, det är bara TypeScript som säger till mig att det finns ett fel även om det inte verkar finnas något.

Slutligen vill jag notera att jag kan göra lite typomvandlingar på det här sättet, och då försvinner felen, även om jag helst inte skulle vilja göra det om jag inte absolut måste:

switch (action.payload.event) {
  case 'receive-message':
    myArray.filter((thread) => thread.property === (action.payload as ReceiveMessageEvent).payload.message.to)
  case 'read-message':
    // ...
  case 'set-threads':
    // ...
}

Finns det en anledning till detta beteende, eller är detta potentiellt ett fel i TypeScript?

Svar #1

Det är som om Typescript tappar sin typkunskap när jag går in i callback-funktionen

Ja, det är det. Och det beteendet är avsiktligt. Eller åtminstone är det det bästa TypeScript kan göra.

Problemet är att TypeScript inte har någon aning om när återanropsfunktionen kommer att kallas. Du och jag vet att för .filter kommer återanropsfunktionen att kallas synkront utan något emellan, men det är inte fallet för alla återanrop. Till exempel kommer återanropet i setTimeout att kallas asynkront.

Typinformationen identifierar inte om det är synkront eller inte, så TypeScript måste anta det värsta fallet: asynkront. Om en callback anropas asynkront kan vilken som helst godtycklig kod ha körts innan callbacken, och därför kanske kontrollen du gjorde för att minska typen inte längre är korrekt. Någon kod kan ha ändrat egenskaperna under tiden.

Den enklaste lösningen på detta är vanligtvis att tilldela värdet du bryr dig om till en const medan du fortfarande är i icke-callbackkod, och sedan hänvisa till const i callback-koden. Med en const kan TypeScript då anta att det inte har blivit omtilldelat.

case 'receive-message':
    const test = action.payload.payload.message.to
    myArray.filter((thread) => thread.property === test)

// OR:

case 'receive-message':
    const payload = action.payload.payload
    myArray.filter((thread) => thread.property === payload.message.to)

Lekplatslänk

Relaterade artiklar