typescript - Kuinka käyttää sisäkkäistä tyyppiä: K laajentaa keyof T, T[K], esim. T[K]["someSubField"]


Minulla on seuraava funktio:

const fieldDefinitions = {
  a: { id: "14", "default": "none" },
  b: { id: "15", "default": 0 },
  c: { id: "16", "default": 0 },
};

const setFields = <T extends typeof fieldDefinitions, K extends keyof T>(fieldValues: { fieldName: K, value: T[K]["default"] }[]) => {
  //logic to set fields
}

Haluan kirjoittaa sen tavalla, joka hyväksyy vain kenttäarvot, jotka ovat saman tyyppisiä kuin oletusarvo.

Esim.:

setFields([
  { fieldName: "a", value: "somestring" },
  { fieldName: "c", value: 123 },
])
//should work because:
// - default of a is "none" which is a string
// - default of c is 0 which is a number


setFields([
  { fieldName: "a", value: 123 },
  { fieldName: "c", value: "somestring" },
])
//should NOT work because:
// - default of a is "none" which is a string, but a number is given
// - default of c is 0 which is a number, but a string is given

Tässä kohtaa saan virheen tyyppimääritelmässä:

value: T[K]["default"]

Siinä sanotaan: Tyyppi '"default"' ei voi olla käytössä indeksinä tyypille 'T[K]'

Joten kysymykseni on, mikä on oikea tapa päästä käsiksi T[K]:n "default"-ominaisuuden tyyppiin tässä?

Tässä on koodi TypeScript Playgroundsissa:

Vastaus #1

Jos T laajentaa typeof fieldDefinitions, se tarkoittaa, että se voi olla mikä tahansa alityyppi, jolla on muita ominaisuuksia, joilla ei ole objektityyppiä default.

T ei oikeastaan ole käytössä, haluat vain fieldDefinitions, ei sen alityyppiä, joten voit tehdä seuraavasti:

type FieldDefinitions = typeof fieldDefinitions
/**
 * The field value should be enforced to be the same type as the default value in the definition
 */
const setFields = <K extends keyof FieldDefinitions>(fieldValues: { fieldName: K, value: FieldDefinitions[K]["default"] }[]) => {
  //logic to set fields
}

Leikkipuiston linkki

Kuitenkin tämä ei todellisuudessa ratkaise ongelmaa, jota yrität ratkaista. Se ei varmistaa, että taulukon elementissä avain ja arvo ovat korreloituneet (arvo (value) tyypitetään kaikkien nykyisten avainten yhdistelmänä)

Todellinen ratkaisu tässä on käyttää parametrina kartoitettua tyyppiä. Tätä tekniikkaa, jota joskus kutsutaan käänteiseksi kartoitetyypiksi, käytettäessä TS pystyy seuraamaan korrelaatiota jokaisen taulukon elementin sisällä:

const fieldDefinitions = {
  a: { id: "14", "default": "none" },
  b: { id: "15", "default": 0 },
  c: { id: "16", "default": 0 },
};

type FieldValues<T> = {
    [P in keyof T]: { fieldName: T[P], value: typeof fieldDefinitions[T[P] & FieldDefinitionKeys]['default'] }
}

type FieldDefinitionKeys = keyof typeof fieldDefinitions
const setFields = <T extends [FieldDefinitionKeys] | FieldDefinitionKeys[]>(fieldValues: FieldValues<T>) => {
}



// works
setFields([
  { fieldName: "a", value: "somestring" },
  { fieldName: "c", value: 123 },
])

//does not work
setFields([
  { fieldName: "a", value: 123 },
  { fieldName: "c", value: "somestring" },
])

Leikkipuiston linkki