Typescript - Typavsmalning när man loopar över nycklarna på ett objekt


När jag måste iterera över nycklarna i mappedTypes, har jag alltid svårt att få Typescript att begränsa typen på värdet som är kopplat till nyckeln jag itererar över och slutar alltid med ett ts-ignore ...

Ett kodexempel som visar problemet är värt tusen ord :) Här är det!

type Person = {
  firstName?: string
  lastName?: string
  age?: number
}

type Update<T> = {
  before: T,
  after: T,
}

type Updates = { [Key in keyof Person]: Update<Person[Key]> }

/**
 * Dummy transformers for illustration purposes
 */
const transformers : {[Key in keyof Person]: (value: Person[Key]) => Person[Key]} = {
  firstName: value => value?.toUpperCase(),
  lastName: value => value?.toUpperCase(),
  age: value => typeof value === 'undefined'? 0 : value + 2
}

/**
 * Input: {firstName: 'david', age: 23}
 * Output: {firstName: {before: 'david', after: 'David'}, age: {before: 22, after: 24}}
 * @param person
 */
function enrichPerson (person: Person): Updates {
  return Object.keys(person).reduce(
    (previousUpdates, key) => {
      const typedKey = key as keyof Person
      const result = { ...previousUpdates }

      result[typedKey] = {
        before: person[typedKey],                           // <--- Typing problem Here
        after: transformers[typedKey](person[typedKey]),    // <--- Typing problem Here
      }

      return result
    },
    {} as Updates
  )
}

export {}

Vad kan jag göra för att begränsa typen av värdet? Om det inte är möjligt, vilket mönster skulle du använda i det här fallet?

Tack så mycket för din hjälp! Det här problemet plågar mig!

Svar #1

Jag har lagt till en typ till Updatetable<Type> och en annan funktion update för att göra tilldelning typ-säker. Jag hoppas att detta fungerar för dig:

type Person = {
    firstName?: string;
    lastName?: string;
    age?: number;
};

type Update<T> = {
    before: T;
    after: T;
};

type Updatetable<Type> = { [Key in keyof Type]: Update<Type[Key]> };
type Updates = Updatetable<Person>;

/**
 * Dummy transformers for illustration purposes
 */
const transformers: { [Key in keyof Person]: (value?: Person[Key]) => Person[Key] } = {
    firstName: (value) => value?.toUpperCase(),
    lastName: (value) => value?.toUpperCase(),
    age: (value) => (typeof value === "undefined" ? 0 : value + 2),
};

/**
 * Input: {firstName: 'david', age: 23}
 * Output: {firstName: {before: 'david', after: 'David'}, age: {before: 22, after: 24}}
 * @param person
 */
function enrichPerson(person: Person): Updates {
    return Object.keys(person).reduce<Updates>((previousUpdates, key) => {
        const typedKey = key as keyof Person;
        const result: Updates = { ...previousUpdates };

        update(result, typedKey, { before: person[typedKey], after: person[typedKey] });

        return result;
    }, {});
}

function update<Type, Key extends keyof Type>(result: Updatetable<Type>, key: Key, value: Update<Type[Key]>): void {
    result[key] = value;
}

export {};

Svar #2

Beröm till @okan aslan för att ha pekat mig i rätt riktning. Tack igen! Här är en modifierad version av kodexemplet som löser problemet.

Titta på introduktionen av de två hjälpfunktionerna update och applyTransformer.

type Person = {
  firstName?: string;
  lastName?: string;
  age?: number;
};

type Update<T> = {
  before: T;
  after: T;
};

type Updatetable<Type> = { [Key in keyof Type]: Update<Type[Key]> };
type Updates = Updatetable<Person>;

type Transformer<Type, Key extends keyof Required<Type>> = (x: Type[Key]) => Type[Key]
type Transformers<Type> = { [Key in keyof Required<Type>]: Transformer<Type, Key> }


function update<T, K extends keyof T> (obj: Updatetable<T>, key: K, newValue: Update<T[K]>): void {
  obj[key] = newValue
}

function applyTransformer<T, K extends keyof Required<T>> (
  transformers: Transformers<T>,
  key: K,
  input: T[K]
): T[K] {
  return transformers[key](input)
}


/**
 * Dummy transformers for illustration purposes
 */
const transformers: { [Key in keyof Required<Person>]: (value?: Person[Key]) => Person[Key] } = {
  firstName: (value) => value?.toUpperCase(),
  lastName: (value) => value?.toUpperCase(),
  age: (value) => (typeof value === 'undefined' ? 0 : value + 2),
}

/**
 * Input: {firstName: 'david', age: 22}
 * Output: {firstName: {before: 'david', after: 'David'}, age: {before: 22, after: 24}}
 * @param person
 */
function enrichPerson (person: Person): Updates {
  return Object.keys(person).reduce<Updates>((previousUpdates, key) => {
    const typedKey = key as keyof Person
    const result: Updates = { ...previousUpdates }

    update(result, typedKey, {
      before: person[typedKey],
      after:  applyTransformer(transformers, typedKey, person[typedKey])
    })

    return result
  }, {})
}

export {}