Hvordan skrive en ordbok laget fra et objekt i TypeScript?


Ganske ofte bruker jeg objekter som ordbøker f.eks.

type Foo = { a: string }

type MyDictionary = { [key: string]: Foo }

function foo(dict: MyDictionary) {
  // Requirement 1: Should be of type Foo[]
  const values = Object.values(dict)

  // Requirement 2: Should be of type Foo | undefined
  const aValue = dict.b

  // Requirement 3: Should fail
  dict.c = undefined
}

Løsning 1

Mitt standardvalg er

type MyDictionary = Record<string, Foo> // aka { [key: string]: Foo }

Dette vil gå gjennom 1 og 3, men feile på 2.

Løsning 2

{ [key: string]: Foo | undefined }

Dette vil passere 2, men feile på 1 og 3.

Løsning 3

Et annet alternativ er å holde seg til alternativ 1, men bruke en getter-funksjon for å gjøre ting tryggere

export function safeGet<T extends {}, K extends keyof T>(obj: T, key: K): T[K] | undefined {
  return obj[key]
}

safeGet(dict, 'b')

Noen andre forslag?

Merk: Jeg vet at Map gjør dette godt, men å bruke Map er ikke alltid en praktisk refaktorering. Dette spørsmålet gjelder spesifikt objekter som ordbøker.

Svar #1

Du ser etter funksjonaliteten som tilbys av --noUncheckedIndexedAccess kompilatoralternativet, som legger til undefined i domenet for typen du får når du leser en egenskap dekket av en indeksignatur, uten også å gjøre det for typen du får lov til å skrive til den samme egenskapen. Og merk at det ikke påvirker returtypen til Object.values(), hvis deklarasjon er

interface ObjectConstructor {
  values<T>(o: { [s: string]: T } | ArrayLike<T>): T[];
}

Merk at dette kompileralternativet ikke er en del av --strict-pakken med kompileralternativer, og blir derfor ikke betraktet som et "standard" type-sikkerhetsalternativ. Det er fordi, selv om det noen ganger kan være ønskelig å beskytte mot undefined på denne måten, kan det være ganske irriterende i andre tilfeller, ved å generere feil i veldig vanlig idiomatisk JavaScript.

For eksempel har array-typer i TypeScript en number indekssignatur. Å aktivere --noUncheckedIndexedAccess hindrer det svært vanlige bruksområdet med å iterere gjennom et array ved å løkke gjennom dets numeriske indekser:

const arr = ["a", "b", "c", "d", "e"];
for (let i = 0; i < arr.length; i++) {
    console.log(arr[i].toUpperCase()); // error!
    // -------> ~~~~~~ Object is possibly undefined
}

Teknisk sett eksisterer sporadiske matriser, så man kunne hevde at denne feilen burde skje... men i praksis er det ofte mer problemer enn det er verdt å la kompilatoren gjøre slike kontroller.

Så hvis du vil ha denne sjekken, må du aktivere den eksplisitt.

Lenke til kode på lekeplassen

Svar #2

API-en du leter etter tilbys av Map.

type Foo = { a: string }

type MyDictionary = Map<string, Foo>();

function foo(dict: MyDictionary) {
  // Requirement 1: Should be of type Foo[]
  const values = [...dict.values()]

  // Requirement 2: Should be of type Foo | undefined
  const aValue = dict.get("b")

  // Requirement 3: Should fail
  dict.set("c", undefined)
}

Lekeplass

Relaterte artikler