javascript - Tildeling som involverer en generell egenskap av et generisk objekt mislykkes i å typekontrollere korrekt innenfor en generell funksjon
Jeg har en generell funksjon som leser eller skriver den egenskapen som kaller har valgt for et gitt objekt. Jeg bruker typebegrensninger for å sikre at nøkkelen som sendes inn, er for en egenskap som kan tildeles til eller fra den relevante typen. Koden som kaller funksjonen ser ut til å typekontrollere korrekt. Bruken av objektets egenskap innen implementeringen typekontrollerer ikke som forventet.
I dette eksempelet bruker jeg boolean som den forventede typen. Jeg har kommentert linjene som ikke sjekker typen som forventet. Du kan også se dette eksempelet i TypeScript-lekeplassen her.
Hvordan kan jeg uttrykke signaturen til booleanAssignmentTest slik at typekontrollen forstår at obj[key] har typen boolean? Kan det gjøres på en måte som holder boolean selv generisk, for å tillate at flere slike lignende funksjoner, som fungerer med andre typer, kan defineres på en ensartet måte?
type KeysOfPropertiesWithType<T, U> = {
// We check extends in both directions to ensure assignment could be in either direction.
[K in keyof T]: T[K] extends U ? (U extends T[K] ? K : never) : never;
}[keyof T];
type PickPropertiesWithType<T, U> = Pick<T, KeysOfPropertiesWithType<T, U>>;
function booleanAssignmentTest<T extends PickPropertiesWithType<T, boolean>, K extends KeysOfPropertiesWithType<T, boolean>>(obj: T, key: K): void {
let foo: boolean = obj[key]; // Fine!
let foo2: string = obj[key]; // No error, but there should be!
obj[key] = true; // Error: "Type 'true' is not assignable to type 'T[K]'."
}
let foo = { aBool: false, aNumber: 33, anotherBool: false };
booleanAssignmentTest(foo, "aBool"); // Fine!
booleanAssignmentTest(foo, "anotherBool"); // Fine!
booleanAssignmentTest(foo, "aNumber"); // Error: working as intended!
Jeg bruker tsc versjon 3.4.5 i tilfelle det er relevant.
Oppdatering:
Jeg fant følgende svar på et lignende problem: https://stackoverflow.com/a/52047487/740958
Jeg prøvde å bruke deres tilnærming som er enklere og fungerer litt bedre, men setningen obj[key]=true; har fortsatt det samme problemet.
function booleanAssignmentTest2<T extends Record<K, boolean>, K extends keyof T>(obj: T, key: K): void {
let foo: boolean = obj[key]; // Fine!
let foo2: string = obj[key]; // Error: working as intended!
obj[key] = true; // Error: "Type 'true' is not assignable to type 'T[K]'."
}
let foo = { aBool: false, aNumber: 33, anotherBool: false };
booleanAssignmentTest2(foo, "aBool"); // Fine!
booleanAssignmentTest2(foo, "anotherBool"); // Fine!
booleanAssignmentTest2(foo, "aNumber"); // Error: working as intended!
Dette ^^eksempelet på TS Playground.
Svar #1
Det første alternativet (ved å bruke KeysOfPropertiesWithType) fungerer ikke fordi typescript ikke kan resonnere om betingede typer som fortsatt inneholder uavklarte typeparametere (som T og K i dette eksemplet)
Det andre alternativet fungerer ikke fordi T extends Record<K, boolean> betyr at T for eksempel kan være { a: false }, noe som ville bety at tilordningen obj[key]=true ikke ville være gyldig. Generelt betyr det faktum at T[K] må utvide en type ikke at vi inne i den generiske funksjonen kan tilordne en hvilken som helst verdi til den; begrensningen forteller oss bare hva minimumskravet er for verdien, vi kjenner ennå ikke hele kontrakten T[K] krever.
En løsning som faktisk fungerer, i det minste for din eksempelkode, er å ikke bruke T i det hele tatt. Det virker ikke nødvendig i denne sammenhengen:
function booleanAssignmentTest2<K extends PropertyKey>(obj: Record<K, boolean>, key: K): void {
let foo: boolean = obj[key]; // Fine!
let foo2: string = obj[key]; // Error: working as intended!
obj[key] = true; // Ok now we know T[K] is boolean
}
let foo = { aBool: false, aNumber: 33, anotherBool: false };
booleanAssignmentTest2(foo, "aBool"); // Fine!
booleanAssignmentTest2(foo, "anotherBool"); // Fine!
booleanAssignmentTest2(foo, "aNumber"); // Error: working as intended!
Hvis eksemplet ditt er mer komplisert, vennligst gi et komplett eksempel, selv om løsningen generelt vil være å bruke en typepåstand hvis du er sikker på at verdien kan tildeles T[K], så dette er en mulig løsning:
function booleanAssignmentTest2<T extends Record<K, boolean>, K extends keyof T>(obj: T, key: K): void {
let foo: boolean = obj[key]; // Fine!
let foo2: string = obj[key]; // Error: working as intended!
obj[key] = true as T[K]; // ok now
}
