reactjs - Hvordan definere typen til et objekt for å kun akseptere nøkler fra en enum?


Jeg har en enum:

enum MyEnum {
    One,
    Two,
    Three
}

Da har jeg en metode i React-komponenten som skal returnere et objekt, hvis nøkler kun kan være strengenøkler fra MyEnum (det vil si, så langt: One, Two, Three):

myFunction = (someValue: {}[]): {} => {
    return someValue.reduce((res, v, i) => {
         res[v.name].push(v)
         return res
    }, {})
}

Spørsmål: Hvordan kan jeg dynamisk definere typen på returverdien til denne funksjonen slik at det er et objekt, der nøklene kun kan være strengnøkler av MyEnum?

Akkurat nå er det bare 3 av dem, men hvis jeg legger til en til i enumen, bør typen på returverdien til myFunction også oppdateres.

Oppdatering 1:

Prøvde å tilordne Record<keyof typeof MyEnum, any> som en returverdi:

myMethod = (someValue: {}[]): Record<keyof typeof MyEnum, any> => {
    return someValue.reduce((res: any, v: SomeInterface) => {
         res[v.name].push(v)
         return res
    }, {})
}

Dette gir ingen feil, men hvis jeg tilordner returverdien av reduce til en variabel og deretter legger til en annen egenskap på den (hvor nøkkelen ikke er MyEnum-nøkkel), vil det ikke gi noen feil, noe som ikke er det jeg ønsker:

myMethod = (someValue: {}[]): Record<keyof typeof MyEnum, any> => {
    let someVar = someValue.reduce((res: any, v: SomeInterface) => {
         res[v.name].push(v)
         return res
    }, {})
    someVar.keyName = 'value'
    return someVar
}

Svar #1

Så, det ser ut til å være mye som skjer her. Mine observasjoner og gjetninger:

  • Siden v er SomeInterface, ser det ut til at someValue må være et array av SomeInterface.

  • Jeg gjetter at res i kroppen til reduce's callback-funksjon er den ønskede returverdien av myMethod, siden du setter verdier på den i callback-funksjonen. Hvis vi kaller returverditypen til myMethod for navnet RetType, betyr det at nøklene til RetType må være keyof typeof MyEnum.

  • Du bruker v.name som de nøklene, og siden v er SomeInterface, antar jeg at SomeInterface's name-egenskap også bør være keyof typeof MyEnum.

  • Du pusher v på verdiene til res, så du forventer at egenskapene til res er arrays av SomeInterface. Derfor er RetType noe som Record<keyof typeof MyEnum, SomeInterface[]>, hvor Record<K,V> er en type fra standardbiblioteket som representerer et objekt med nøkler i K og verdier i V.

  • Men selvfølgelig, siden someValue kan være et tomt array, eller ellers ikke ha så mange elementer som nøklene til MyEnum, kan RetType mangle noen egenskaper. Så det er mer som Partial<Record<keyof typeof MyEnum, SomeInterface[]>>, hvor Partial<T> er en type fra standardbiblioteket som gjør egenskapene i T valgfrie.

  • Nå er jeg ikke sikker på om du fullt ut forstår hvordan reduce fungerer, men callbacken må returnere noe av samme type som den tiltenkte returverdien. Og det andre argumentet er "startverdi" som også må være av samme type som den tiltenkte returverdien. Derfor må jeg endre callbacken din slik at den returnerer res, og startverdien slik at den også er av typen RetType.

  • Jeg legger også merke til at siden startverdien er et tomt objekt, vil første gang du prøver å push noe på en av dets array-egenskaper, finne ut at det er undefined. Så du må sjekke det og sette det til en tom array før du prøver å push på det.

Det leder meg til noe slik:

enum MyEnum {
  One,
  Two,
  Three
}

interface SomeInterface {
  name: keyof typeof MyEnum
};

type RetType = Partial<Record<keyof typeof MyEnum, SomeInterface[]>>

const myMethod = (someValue: SomeInterface[]): RetType => {
  let someVar = someValue.reduce((res: RetType, v: SomeInterface) => {
    const resVName = res[v.name] || []; // make sure it's set
    resVName.push(v);
    res[v.name]=resVName; // copy back in case it's a new array
    return res;
  }, {} as RetType)
  //someVar.keyName = 'value' // errors now
  return someVar
}

Merk at jeg kopierer til og fra en ny verdi kalt resVName. Dette lar kompilatorens kontrollflytanalyse fungere og være sikker på at resVName faktisk er en SomeInterface[] og ikke en SomeInterface[] | undefined. Det fungerer ikke på res[v.name] på grunn av en feil/begrensning i TypeScript hvor kontrollflytanalyse ikke fungerer med hakeparentes-notasjon for egenskapsadgang. Det er litt irriterende, men å sikre typesikkerhet er sannsynligvis verdt det.

Den koden er godt typet og kan muligens fungere ved kjøring (du må sjekke det). Men reduce virker ikke som det du prøver å gjøre. I stedet vil jeg si at du ser etter den enklere forEach-metoden, slik:

const myMethod = (someValue: SomeInterface[]): RetType => {
  let someVar: RetType = {};
  someValue.forEach((v: SomeInterface) => {
    const someVarVName = someVar[v.name] || []; // make sure it's set
    someVarVName.push(v);
    someVar[v.name] = someVarVName; // copy back in case it's a new array
  });
  //someVar.keyName = 'value'
  return someVar
}

Det fungerer også og krangler ikke med å sende rundt akkumulatoren. Begge måter er nok greit.

Ok, håper det gir mening og gir deg noen ideer. Lykke til!

Relaterte artikler