typescript - Overraskende typefejl ved forsøg på at opdatere en tagget union


Jeg ville forvente, at koden nedenfor fungerer fint, men linje 7 giver en fejl med beskeden Type '"one"' er ikke tildelbar til typen '"two"'.:

type One = { type: 'one', value: number }
type Two = { type: 'two', value: number }

type OneOrTwo = One | Two

function handleOneOrTwo(oneOrTwo: OneOrTwo): OneOrTwo {
  const nextOneOrTwo: OneOrTwo = {
    type: oneOrTwo.type,
    value: oneOrTwo.value + 1,
  }
  return nextOneOrTwo
}

Dette er ret overraskende. Især i betragtning af, at når jeg opdaterer funktionen til at bruge en switch-erklæring (med identisk funktionalitet), virker det:

type One = { type: 'one', value: number }
type Two = { type: 'two', value: number }

type OneOrTwo = One | Two

function handleOneOrTwo(oneOrTwo: OneOrTwo): OneOrTwo {
  const nextOneOrTwo = {
    value: oneOrTwo.value + 1,
  }

  switch (oneOrTwo.type) {
    case 'one':
      return { ...nextOneOrTwo, type: oneOrTwo.type }
    case 'two':
      return { ...nextOneOrTwo, type: oneOrTwo.type }
    default:
      throw unreachable(oneOrTwo)
  }
}

function unreachable(value: never) {
  return new Error(value)
}

Selv typecasting virker ikke (type: oneOrTwo.type as 'one' | 'two').

Jeg har læst svarene på Type Error, når man prøver at udtrække type fra unioner, men jeg er stadig usikker på, hvorfor denne fejl opstår, og hvordan man retter den.

Svar #1

Årsagen til, at du får en fejl, er fordi når du tilgår type af unionen, er det one | two. Men når du tildeler objektrelationen, fungerer kontrollen på den måde, at hvis objektrelationen matcher mindst ét medlem af unionen, er tildelingen gyldig.

Så lad os se. Typen af objekt-litteralen er { type : 'one' | 'two' value: number }.

Er objekt-literaltypen kompatibel med One? Nej, type er 'one' | 'two' i objekt-literalet, men det er 'one' i One

Er objekt-literaltypen kompatibel med Two? Nej, type er 'one' | 'two' i objekt-literalet, men det er 'two' i Two

Så ender du med en objektlitteral, der ikke kan tildeles nogen af medlemmerne af foreningen.

En mulighed uden typepåstand er at bruge et spread-udtryk, som vil bevare den oprindelige unionstype:

function handleOneOrTwo(oneOrTwo: OneOrTwo): OneOrTwo {
  const nextOneOrTwo: OneOrTwo = {
     ...oneOrTwo,
    value: oneOrTwo.value + 1,
  }
  return nextOneOrTwo
}

Eller for at bruge en typepåstand:

function handleOneOrTwo(oneOrTwo: OneOrTwo): OneOrTwo {
   const nextOneOrTwo: OneOrTwo = {
     type: oneOrTwo.type,
     value: oneOrTwo.value + 1,
   } as OneOrTwo
   return nextOneOrTwo
} 

Relaterede artikler