javascript - Typescript-fejl, når flere valgfrie typer bruges


Dette er et eksempel på fejlen:

interface Block {
  id: string;
}

interface TitleBlock extends Block  {
  data: {
    text: "hi",
    icon: "hi-icon"
  }
}

interface SubtitleBlock extends Block  {
  data: {
    text: "goodbye",
  }
}

interface CommentBlock extends Block {
  author: string; 
}

type BlockTypes = TitleBlock | SubtitleBlock | CommentBlock

function mapper(block : BlockTypes) {
    if(block?.data?.text){  // TS Error here!!! 
      /** do something */
    }
}

Live eksempel

Jeg vil gøre noget i funktionen, når blokken har et specifikt attribut, men TS tillader mig ikke at gøre det, selv når der er typer i BlockTypes, der har den egenskab.

Hvad er den bedste måde at håndtere denne type funktionaliteter på?

Svar #1

Den valgfrie kædeoperator (?.) beskytter kun mod null og undefined; den filtrerer ikke et union-typet udtryk til de medlemmer, der er kendt for at indeholde den nøgle. Se denne kommentar i Microsoft/TypeScript#33736 for et kanonisk svar.


Problemet er, at objekttyper i TypeScript er åbne og må indeholde flere egenskaber, end compileren kender til. Så hvis block.data er sandt, betyder det desværre ikke, at block.data.text eksisterer:

const oof = {
  id: "x",
  author: "weirdo",
  data: 123
}
mapper(oof); // accepted

Her er oof en gyldig CommentBlock, og derfor er mapper(oof) tilladt, og oof.data er sand, men oof.data.text er undefined, og du vil få et problem, hvis du prøver at behandle det som en string.

Så det ville være usikkert (det vil sige, ikke typesikkert) at tillade valgfri kædning til at filtrere unioner på den måde, du ønsker.


Løsningen her, hvis du ikke tror, at oof-lignende tilfælde er sandsynlige, er at bruge in-operatoren som en typebeskytter:

function mapper(block: BlockTypes) {
  if ("data" in block) {
    block.data.text.toUpperCase()
  }
}

Dette fungerer fint uden compiler-fejl. Selvfølgelig er det stadig usikkert på præcis samme måde som indsnævring med ?. ville være... hvis du kalder mapper(oof), er der ingen compiler-fejl, men du vil få en runtime-fejl. Så du bør være forsigtig.

Det er lidt mærkeligt, at TypeScript tillader, at in opfører sig på denne måde, men ikke tillader andre lignende konstruktioner. Du kan læse diskussionen i microsoft/TypeScript#10485 for at se, hvorfor det skete. For det meste ser det ud til, at når folk skriver "foo" in bar, gør de sjældent noget forkert med det, men at andre konstruktioner som bar.foo && bar.foo.baz misbruges oftere. Men det er ifølge TS-teamet, ikke mig. 🤷♂️

Playground-link til kode

Relaterede artikler