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 {}
