Skapa en generell TypeScript-funktion som tilldelar ett värde till ett objektets egenskap
Jag vill skapa en enkel funktion som tar nyckel för ett specifikt objekt och värde för respektive nyckel och tilldelar det nya värdet till objektet. Något liknande detta:
interface MyObject {
key1: string
key2: number
}
const object: MyObject = {
key1: 'abc',
key2: 100
}
const updateObjectProperty = (key: keyof MyObject, value: MyObject[keyof MyObject]) => {
object[key] = value
}
Men i det här fallet fungerar det inte. TypeScript-kompilatorn visar ett fel:
Type 'string | number' is not assignable to type 'never'.
Jag känner att kanske några generiska lösningar potentiellt skulle kunna lösa problemet här, men jag har svårt att lista ut hur.
Svar #1
Om du binder parametrarna tillsammans med hjälp av en generisk typparameter kommer det att kompileras:
const updateObjectProperty = <K extends keyof MyObject>(key: K, value: MyObject[K]) => {
object[key] = value
}
updateObjectProperty("key1", "") //ok
updateObjectProperty("key2", "") // err
updateObjectProperty("key2", 2) // ok
Lekplatslänk
Anledningen till att din version inte fungerar är att du kan skicka in okorrelerade nyckel och värde:
updateObjectProperty("key2", "") // key2 now has a string
Lekplatslänk
Nu är vår nya version inte 100% säker heller, du skulle kunna kalla den med key union, men det är mindre vanligt:
let k: keyof MyObject = Math.random() > 0.5 ? "key1" : "key2"
let v: MyObject[keyof MyObject] = Math.random() > 0.5 ? 0 : "key2"
updateObjectProperty(k, v); // fails when key and value are mssmatched
Lekplatslänk
Du skulle kunna göra samma sak som TS gör och skriva värdet som en korsning av möjliga egenskapsvärden, och detta kommer också att fånga detta kryphål, men det skulle kräva en antydan i implementeringen igen:
type ValueIntersectionByKeyUnion<T, TKey extends keyof T> = {
[P in TKey]: (k: T[P])=>void
} [TKey] extends ((k: infer I)=>void) ? I : never
const updateObjectProperty = <K extends keyof MyObject>(key: K, value: ValueIntersectionByKeyUnion<MyObject, K>) => {
object[key] = value as MyObject[K];
}
let k: keyof MyObject = Math.random() > 0.5 ? "key1" : "key2"
let v: MyObject[keyof MyObject] = Math.random() > 0.5 ? 0 : "key2"
updateObjectProperty(k, v); //err
updateObjectProperty("key1", "") //ok
updateObjectProperty("key2", "") // err
updateObjectProperty("key2", 2) // ok
Lekplatslänk
Svar #2
Problemet är att du inte specificerar ett förhållande mellan key och value. Du säger att key kan vara vilken egenskap som helst i MyObj och att value kan vara typen av vilken som helst av objektets egenskaper.
Om du definierar en generisk T som keyof MyObject och använder den för att definiera båda parametrarna, kommer du att få det beteende du letar efter.
Se ett levande exempel
function updateObjectProperty<T extends keyof MyObject>(key: T, value: MyObject[T]) {
object[key] = value;
}
Observera de kråkstreck som visar att ogiltiga parametrar har skickats.
