typescript - Angi egenskapstype


Er det mulig å sette typen til en egenskap i TypeScript uten å caste den? Vurder denne koden:

interface Overlay {
    type: "modal" | "drawer"
    other?: number
}

const rec = {
    obj1: { type: "modal" },
    obj2: { type: "drawer" },
    ...
}

Objektet rec har typen { obj1: { type: string }, obj2: { type: string } }. Jeg vil at det skal ha typen { obj1: Overlay, obj2: Overlay, ... }.

Mine forsøk:

  1. Ok - men veldig omstendelig og legger til variabler
const obj1: Overlay = { type: "modal" }
const obj2: Overlay = { type: "drawer" }
const ...
const rec = { obj1, obj2, ... }
  1. Dårlig - tillater at feil skjules (f.eks. {} as Overlay vil bli vurdert som ok av TS). Å bruke satisfies i stedet setter ikke typen, men sjekker den på en per-egenskap basis.
const rec = {
    obj1: { type: "modal" } as Overlay,
    obj2: { type: "drawer" } as Overlay,
    ...
}
  1. Dårlig - mister nøkkeltypeinformasjonen ("obj1" | "obj2" | ... blir string)
const rec: Record<string, Overlay> = {
    obj1: { type: "modal" },
    obj2: { type: "drawer" },
    ...
}
  1. Dårlig - mister informasjon om 'other'-egenskapen og setter feilaktig typen til 'type' til enten "modal" eller "drawer" i stedet for unionstypen
const rec = {
    obj1: { type: "modal" },
    obj2: { type: "drawer" },
    ...
} as const

Finnes det en bedre måte å oppnå riktig type enn #1?

Svar #1

Opptak

Du kan bruke Record-typen for dette, slik:

type AcceptedKey = 'obj1' | 'obj2' | 'obj3'

interface Overlay {
  type: 'modal' | 'drawer'
  other?: number
}

const rec: Record<AcceptedKey, Overlay> = {
  obj1: { type: 'modal' },
  obj2: { type: 'drawer' },
  obj3: { type: 'drawer' },
}

Objekter som sendes til hver egenskap i "Record" må nå samsvare med Overlay-typen. I tillegg må hvert element fra AcceptedKey eksistere som en nøkkel i Record.

Spesifikasjonen til Record er som følger:

 /**
 * Construct a type with a set of properties K of type T
 */
type Record<K extends keyof any, T> = {
    [P in K]: T;
};

Delvis rekord

Hvis du også vil at AcceptedKey skal brukes som valgfrie nøkler, kan du bruke Partial sammen med Record på denne måten:

const partialRec: Partial<Record<AcceptedKey, Overlay>> = {
  obj1: { type: 'modal' },
  obj2: { type: 'drawer' },
}

Dette kan være et godt alternativ hvis du har et begrenset og forhåndsbestemt antall nøkler.

Nøkkelformat i stedet for spesifikke nøkler

Hvis du trenger typen til nøkkelen i et format i stedet for konkrete verdier, kan du legge inn en number-type i en string-type slik:

type AcceptedKeyFormat = `obj${number}`

interface Overlay {
  type: 'modal' | 'drawer'
  other?: number
}

const rec2: Record<AcceptedKeyFormat, Overlay> = {
  obj0: { type: 'modal' },
  obj1: { type: 'drawer' },
  obj999: { type: 'drawer' },
}

Dette betyr også at du ikke trenger å legge til hver nøkkel til typen også. Dette kan være et godt alternativ hvis hver post kan ha forskjellige nøkler.