Få typen til en spesifikk nøkkel av generisk type i TypeScript


Jeg sliter med å få en kompleks typefunksjonalitet ut av denne updateArray generiske funksjonen jeg bygger:

// Updates an object array at the specified update key with the update value,
// if the specified test key matches the test value.
// Optionally pass testFailValue to set a default value if the test fails.
// Note that by passing a testFailValue ALL elements in the array will be updated at the specified update property. 
// If it is omitted only elements passing the test will be updated.
export const updateArray = <T, U, V>(options: {
  array: Array<T>
  testKey: keyof T
  testValue: U
  updateKey: keyof T
  updateValue: V
  testFailValue?: V
}): Array<T> => {
  const {
    array,
    testKey,
    testValue,
    updateKey,
    updateValue,
    testFailValue,
  } = options
  return array.map(item => {
    if (item[testKey] === testValue) {
      item[updateKey] = updateValue
    } else if (testFailValue !== undefined) {
      item[updateKey] = testFailValue
    }
    return item
  })
}

TypeScript vil klage her både i if-setningen og de to tildelingssetningene, men i en kallsignatur vil det ikke klage og er den strenge typetilkontrollen jeg ser etter akkurat, for eksempel:

interface IMyInterface {
    propertyA: string
    prepertyB: boolean
}

updateArray<IMyInterface, IMyInterface['propertyA'], IMyInterface['propertyB']>({
    array: state.editors[editor].editorSettings,
    testKey: "propertyA",
    testValue: 'someValue',
    updateKey: "propertyB",
    updateValue: true,
    testFailValue: false
})

Hvis jeg utelater typene U og V, og erstatter dem med T[keyof T], vil ikke Typescript klage:

export const updateArray = <T>(options: {
  array: Array<T>
  testKey: keyof T
  testValue: T[keyof T]
  updateKey: keyof T
  updateValue: T[keyof T]
  testFailValue?: T[keyof T]
}): Array<T> => {
  const {
    array,
    testKey,
    testValue,
    updateKey,
    updateValue,
    testFailValue,
  } = options
  return array.map(item => {
    if (item[testKey] === testValue) {
      item[updateKey] = updateValue
    } else if (testFailValue !== undefined) {
      item[updateKey] = testFailValue
    }
    return item
  })
}

men dette er heller ikke helt korrekt. T[keyof T] er for fleksibel: jeg kunne tildele en 'feil' type til en gitt egenskap (for eksempel, i eksemplet som er gitt, en boolean-verdi til en egenskap som bare skal holde strenger, eller omvendt). Åpenbart er denne typen reassignment oppførsel grei i JavaScript (noe som er en grunn til at TypeScript ikke vil klage), men uønsket for denne funksjonen jeg lager. Det jeg egentlig trenger er en slags typeof T[specific key], for å sikre at testValue, updateValue og testFailValue samsvarer med de riktige typene, men specific key kan endres avhengig av den faktiske typen til T.

Kan noe sånt gjøres?

Svar #1

Du kan legge til en begrensning på U slik at det er en delmengde av nøklene til T ved å bruke extends. V kan representere typen updateKey og ha samme begrensning.

Hvis du forenkler problemet ditt til en funksjon updateObject i stedet for updateArray, vil det bli:

function updateObject<
    T,
    U extends keyof T,
    V extends keyof T,
>(
    obj: T,
    testKey: U,
    testValue: T[U],
    updateKey: V,
    updateValue: T[V],
    testFailValue?: T[V]
) {
    if (obj[testKey] === testValue) {
        obj[updateKey] = updateValue;
    } else if (testFailValue !== undefined) {
        obj[updateKey] = testFailValue;
    }
}

updateObject({aString: 'hello', aNumber: 42}, 'aString', 'hello', 'aNumber', 23);

Relaterte artikler