Typescript: definiera typer utan ett fält


Jag hanterar en äldre datamängd och försöker modellera några meningsfulla typer i TypeScript. I det här exemplet, antag att jag har data om arbetslivserfarenhet från ett anställdas CV:

EMPLOY            | START    | END
'my first employ' | 20180901 | 20181001
'my last employ'  | 20180901 | null

Om END är null, betyder det att det är den faktiska anställningen. Eftersom jag har flera affärsregler gällande detta domänkoncept, skulle jag vilja modellera det med typer. Här är min kod:

interface ActualExperience {
  actual: true,
  employ: string,
  start: Date,
  end: undefined // <-- no end! 
}

interface PreviousExperience {
  actual: false,
  employ: string,
  start: Date,
  end: Date     // <-- end has a value!
}

type Experience = ActualExperience | PreviousExperience

Hittills så bra. Sedan vill jag använda mina typer:

// this is OK
const previous: PreviousExperience = {
  actual: false,
  employ: 'my first employ',
  start: new Date(),
  end: new Date()
}

// ERROR! 
const actual: ActualExperience = {
  actual: true,
  employ: 'my last employ',
  start: new Date()
}

Typescript kräver att jag uttryckligen definierar end: undefined för att kunna mappa till ActualEmploy:

// this is OK now!
const actual: ActualExperience = {
  actual: true,
  employ: 'my last employ',
  start: new Date(),
  end: undefined
}

Detta är väldigt opraktiskt för mig, eftersom jag måste uttryckligen lägga till ett odefinierat värde till en post bara för att göra min kompilator nöjd.

Hur kan jag designa en sådan typ av modell?

Svar #1

Det finns två sätt du kan gå tillväga på.

För det första, om det inte finns något specifikt behov av att uttryckligen ha end: undefined i din ActualExperience, kan du bara ta bort det.

För det andra, beroende på vad du försöker göra, kan det vara mer meningsfullt att använda ett ytterligare gränssnitt:

interface BaseExperience {
  actual: boolean,
  employ: string,
  start: Date,
  end?: Date
}

Du kan sedan ange att dina nuvarande gränssnitt implementerar BaseExperience

interface ActualExperience extends BaseExperience {
  actual: true,
  employ: string,
  start: Date
}

interface PreviousExperience extends BaseExperience {
  actual: false,
  employ: string,
  start: Date,
  end: Date
}

Slutligen kan du använda din BaseExperience direkt

const someExperience: BaseExperience = {
  actual: true,
  employ: 'my last employ',
  start: new Date()
}

och använd din ActualExperience på det sätt du vill

const actual: ActualExperience = {
  actual: true,
  employ: 'my last employ',
  start: new Date()
}

Svar #2

deklarera ditt gränssnitt som:

interface ActualExperience {
  actual: true,
  employ: string,
  start: Date
}

om du i senare kod anropar actual.end, kommer JavaScript att returnera undefined, inget behov av att definiera det så på ditt gränssnitt "ActualExperience"