typescript - Diskriminerande unionstyper


Varför kan jag inte särskilja den unionen med bara informationen att medlemmen x är en sträng och inte ett nummer? Varför måste jag använda en litteral typ?

type A = { x: string ; y: string }
type B = { x: number ; y: number }
type C = A | B

function fn(p: C) {
  if (typeof p.x === 'string') { 
    // Typescript is unable to infer p as A
    // Typescript infer p.y as (string | number), why not just string ?
  }
  // Typescript is capable of inferring p.x as number - good
  // But cannot infer p.y as number, why ?
}

För mig är det inte möjligt att kalla funktionen med en typ som denna {x:string,y:number}, så varför antar TypeScript att detta är möjligt?

Svar #1

Om du kan uttrycka typ i klasser, rekommenderar jag att göra det:

class A { x: string; y: string; }
class B { x: number; y: number }
type C = A | B
function fn(p: C) {
    if (p instanceof A) {
        // { x: string; y: string; }
    }
    if (p instanceof B) {
        // { x: number; y: number }
    }

}

I det här fallet kan du använda instanceof-magik. Det här är det bästa sättet. Annars måste du kontrollera alla egenskaper eftersom nyckelordet type endast lägger till definitioner i källan.

Svar #2

Inte direkt en förklaring till varför Typescript-kompilatorn inte kan skilja en unionstyp med typeof, men handboken säger:

Det finns tre ingredienser:

  1. Typer som har en gemensam, singleton-typ-egenskap — diskriminanten.
  2. En typalias som tar unionen av dessa typer — unionen.
  3. Typvakter på den gemensamma egenskapen.

Så för att kunna avgränsa rätt typ är en singleton-typ egenskap nödvändig. Jag skulle använda anpassade typvakter:

type A = { x: string ; y: string }
type B = { x: number ; y: number }
type C = A | B

function isA(obj: C): obj is A { 
  return typeof obj.x === 'string';
}

function isB(obj: C): obj is B { 
  return typeof obj.x === 'number';
}

function fn(p: C) {

  if (isA(p)) {
    // ...
  }

  if (isB(p)) {
    // ...
  }

}

Relaterade artiklar