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
}

Relaterte artikler