Hur man härleder typade mapValues med hjälp av uppslagningar i TypeScript?
Liknande:
Hur man härleder en typad array från en dynamisk nyckelarray i TypeScript?
Jag vill skriva en generisk objekt som tar emot en karta av godtyckliga nycklar till uppslagsvärden och returnerar samma nycklar med typade värden (som en typad _.mapValues).
Möjligheten att få en enskild typad egenskap från ett objekt är dokumenterad och fungerar. Med arrayer måste du hårdkoda överlagringar till typade tupler, men för objekt får jag ett 'Duplicerat strängindexsignatur'-fel.
export interface IPerson {
age: number;
name: string;
}
const person: IPerson = {
age: 1,
name: ""
}
function getProperty<T, K extends keyof T>(o: T, name: K): T[K] {
return o[name];
}
const a = getProperty(person, 'age');
// a: number
const n = getProperty(person, 'name');
// n: string
function getProperties<T, K extends keyof T>(obj: T, keys: { [key: string]: K }) {
const def: { [key: string]: T[K] } = {};
return Object.entries(keys).reduce((result, [key, value]: [string, K]) => {
result[key] = getProperty(obj, value);
return result;
}, def);
}
const { a2, n2 } = getProperties(person, {
a2: 'name',
n2: 'age'
});
// Result:
// {
// a2: string | number,
// n2: string | number
// }
// What I'm looking for:
// {
// a2: string,
// n2: number'
// }
Hur kan detta implementeras med TypeScript?
Svar #1
Så länge det fungerar vid körning, kan du berätta för TypeScript hur man byter namn på nycklar med mappade typer:
type RenameKeys<T, KS extends Record<keyof KS, keyof T>> = {[K in keyof KS]: T[KS[K]]};
function getProperties<T, KS extends Record<keyof KS, keyof T>>(
obj: T,
keys: KS
): RenameKeys<T, KS> {
const def = {} as RenameKeys<T, KS>;
return (Object.entries(keys) as Array<[keyof KS, any]>)
.reduce((result, [key, value]) => {
result[key] = getProperty(obj, value);
return result;
}, def);
}
Detta bör bete sig som du förväntar dig i typ-systemet. Höjdpunkter: typen av keys ges en typ-parameter som heter KS, vilken är begränsad till att vara Record<keyof KS, keyof T>, vilket mer eller mindre betyder "Jag bryr mig inte om vilka nycklarna är, men egenskapstyperna måste vara nycklar från T". Sedan går RenameKeys<T, KS> igenom nycklarna i KS och plockar ut egenskapstyper från T som är relaterade till dem.
Slutligen var jag tvungen att göra några typkontroller... def är RenameKeys<T, KS>. Typen på value i [key, value] gjorde jag just any, eftersom det är svårt för typ systemet att verifiera att result[key] kommer att ha rätt typ. Så det är lite av en kompromiss med implementeringens typ säkerhet... men anroparen av getProperties() borde vara nöjd:
const {a2, n2} = getProperties(person, {
a2: 'name',
n2: 'age'
});
// a2 is string, n2 is number.
Lekplatslänk till kod
Svar #2
Det kan finnas ett sätt att optimera/kollapsa denna typning lite grann, men jag har en funktionsprototyp för getProperties som jag tror uppnår det du letar efter.
Det som saknas i din definition är en stark returtypdefinition som kopplar associationen mellan en specifik nyckel i keys-objektet och den specifika typen i obj-objektet. Eftersom detta saknas blir allt unionen av typer, vilket är beteendet du ser ovan.
Funktionstypen jag har kommit på är:
function getProperties
<T, K extends keyof T, U extends { [name: string]: K }>
(obj: T, keys: U):
{[V in keyof U]: T[U[V]];
Den viktiga delen här är returvärdetypen: {[V in keyof U]: T[U[V]]}
Den specificerar att för varje nyckel V i keys-objektet:
Vkommer att vara en nyckel i utdataobjektetVärdetypen kommer att vara en typ från indata
obj, där typen kommer från nyckeln som ges av värdet som är associerat med nyckelnViU.
