TypeScript: Hur man hanterar generiska typer och keyof-operatorn
Jag försöker skriva en generell funktion som sammanställer uppdateringsdata för databasuuppdateringar.
Överförda argument:
- post att uppdateras
- egenskapsnyckel
- ett nytt arrayelement
Även om jag begränsar nyckelns typ med keyof R, kan jag inte tilldela ett nytt objekt med den nyckeln till en Partial<R>-konstant. Jag får felet Type '{ [x: string]: any[]; }' is not assignable to type 'Partial<R>'. Vad kan jag göra för att få följande kod att fungera? Om jag ersätter den generiska typen R med en icke-generisk typ fungerar det. Men det är inte vad jag behöver.
Utdrag om TypeScript-lekplats
interface BaseRecord {
readonly a: ReadonlyArray<string>
}
function getUpdateData<R extends BaseRecord>(record: R, key: keyof R, newItem: string) {
const updateData: Partial<R> = { [key]: [...record[key], newItem] }
return updateData
}
interface DerivedRecord extends BaseRecord {
readonly b: ReadonlyArray<string>
readonly c: ReadonlyArray<string>
}
const record: DerivedRecord = { a: [], b: [], c: ["first item in c"] }
console.log(getUpdateData<DerivedRecord>(record, "c", "second item in c"))
Svar #1
Du kan alltid böja typ-systemet efter din vilja, antingen genom list (t.ex. indexåtkomst och kompilatorn som antar att R[key] är läs-skriv)
function getUpdateData<R extends BaseRecord>(record: R, key: keyof R, newItem: string) {
var updateData: Partial<R> = {};
updateData[key] = [...record[key], newItem];
return updateData
}
eller brutalkraft (passera genom any-typ):
function getUpdateData<R extends BaseRecord>(record: R, key: keyof R, newItem: string) {
const updateData: Partial<R> = <any> { [key]: [...record[key], newItem] }
return updateData
}
Ovanstående besvarar din fråga, men var försiktig: denna funktion är inte säker. Den antar att varje record som skickas in kommer att ha ett string[]-värde för key-egenskapen, men typen R kanske inte har det. Till exempel:
interface EvilRecord extends BaseRecord {
e: number;
}
var evil: EvilRecord = { a: ['hey', 'you'], e: 42 };
getUpdateData(evil, 'e', 'kaboom'); // compiles okay but runtime error
Dessutom är returvärdestypen Partial<R> lite för bred: du vet att den kommer att ha key-nyckeln, men du kommer att behöva kontrollera den för att typ systemet ska bli nöjt:
var updatedData = getUpdateData<DerivedRecord>(record, "c", "first item in c") // Partial<DerivedRecord>
updatedData.c[0] // warning, object is possibly undefined
Jag skulle föreslå att skriva getUpdateData() så här:
type KeyedRecord<K extends string> = {
readonly [P in K]: ReadonlyArray<string>
};
function getUpdateData<K extends string, R extends KeyedRecord<K>=KeyedRecord<K>>(record: R, key: K, newItem: string) {
return <KeyedRecord<K>> <any> {[key as string]: [...record[key], newItem]};
}
(observera att detta fortfarande är svårt att skriva korrekt på grund av en bugg i TypeScript) Nu kommer funktionen bara att acceptera något där key-egenskapen är av typen ReadonlyArray<string>, och garanterar att key-egenskapen finns i returvärdet:
var evil: EvilRecord = { a: ['hey', 'you'], e: 42 };
getUpdateData(evil, 'e', 'kaboom'); // error, number is not a string array
var updatedData = getUpdateData(record, "c", "first item in c") // KeyedRecord<"c">
updatedData.c[0] // no error
Hoppas det hjälper.
Teknisk uppdatering
Jag ändrade den föreslagna getUpdateData()-deklarationen ovan till att ha två generiska parametrar, eftersom TypeScript av någon anledning tidigare härledde en alltför bred typ för key-parametern, vilket tvingade dig att ange nyckelns typ vid anropsplatsen:
declare function oldGetUpdateData<K extends string>(record: KeyedRecord<K>, key: K, newItem: string): KeyedRecord<K>;
oldGetUpdateData(record, "c", "first item in c"); // K inferred as 'a'|'b'|'c', despite the value of 'c'
oldGetUpdateData<'c'>(record, "c", "first item in c"); // okay now
Genom att lägga till en andra generisk typ fördröjde jag tydligen TypeScripts härledning av posttyp efter att den korrekt har härlett nyckeltypen:
getUpdateData(record, "c", "hello"); // K inferred as 'c' now
Känn dig fri att ignorera detta, men så här görs korv med heuristisk typinferensteori.
