TypeScript: Aksepter alle objekt-nøkler som kartlegger til en spesifikk type
Gitt en objekttype (eller klassetype), vil jeg skrive en funksjon som aksepterer objektet og en liste over nøklene dens. Jeg vil imidlertid bare tillate nøkler som mappes til en verdi av en spesifikk type, f.eks. bare strenger.
Eksempel :
function shouldOnlyAcceptStringValues(o, key) {
// Do something with o[key] that depends on the assumption that o[key] has a specific type, e.g. string
}
const obj = {
a: 1,
b: "test",
c: "bla"
}
const key = "c" as const;
shouldOnlyAcceptStringValues(obj, key); // b and c should be accepted as keys, a not.
Jeg kjenner en måte å sikre at key faktisk eksisterer på o (uavhengig av typen til o[key]):
function shouldOnlyAcceptStringValues<T>(o: T, key: keyof T) {
// Do something with o[key] that depends on the assumption that o[key] has a specific type, e.g. string
}
Dette vil imidlertid også tillate bruk av key="a" selv om det tilsvarer et tall.
Det jeg trenger er noe slik:
function shouldOnlyAcceptStringValues<T, K extends keyof T, T[K] extends string>(o: T, key: K)
Men det er selvfølgelig ikke gyldig TypeScript-kode.
Finnes det et triks for å få det til å fungere? Jeg trenger en måte å ytterligere raffinere settet av nøkler keyof T. Kroppen til funksjonen bør da vite at o[key] er en streng uten å eksplisitt sjekke typen inne i funksjonen. Er det på en eller annen måte mulig?
Svar #1
Hvis du vil ha noe som fungerer både fra kallerens synspunkt og fra implementerens synspunkt, kan du gjøre dette:
function shouldOnlyAcceptStringValues<K extends PropertyKey>(
o: Record<K, string>, key: K
) {
const okay: string = o[key];
}
Dette er en slags bakvendt måte å se på begrensningen din på; i stedet for å begrense key til å være de riktige nøklene fra obj, begrenser du obj til å være et objekt hvis verditype på key er en string. Du kan se at okay aksepteres som en string, og ting fungerer også fra anroperens side:
shouldOnlyAcceptStringValues(obj, "a"); // error!
// ------------------------> ~~~
// Argument of type '{ a: number; b: string; c: string; }' is
// not assignable to parameter of type 'Record<"a", string>'.
shouldOnlyAcceptStringValues(obj, "b"); // okay
shouldOnlyAcceptStringValues(obj, "c"); // okay
Det eneste problemet er at feilen ved det første kallet sannsynligvis ikke er på argumentet du forventer; den klager på obj og ikke "a". Hvis det er greit, flott. Hvis ikke, kan du endre kall-signaturen til å være den typen begrensning du snakker om:
type KeysMatching<T, V> = { [K in keyof T]: T[K] extends V ? K : never }[keyof T]
function shouldOnlyAcceptStringValues2<T>(o: T, key: KeysMatching<T, string>): void;
function shouldOnlyAcceptStringValues2<K extends PropertyKey>(
o: Record<K, string>, key: K
) {
const okay: string = o[key];
}
Typefunksjonen KeysMatching<T, V> tar en type T og returnerer bare de nøklene hvis verdier kan tildeles V. Og dermed vil kallsignaturen spesifisere T for o og KeysMatching<T, string> for key. Legg merke til hvordan jeg har skrevet den kallsignaturen som en enkelt overbelastning, og implementeringssignaturen er den samme som før. Hvis du ikke gjør det, klarer ikke kompilatoren å forstå at for generisk T er T[KeysMatching<T, string>] tildelbar til string; det er en høyordens typeinferenz som kompilatoren ikke kan utføre:
function shouldOnlyAcceptStringValuesOops<T>(o: T, key: KeysMatching<T, string>) {
const oops: string = o[key]; // error!
// -> ~~~~
// Type 'T[{ [K in keyof T]: T[K] extends string ? K : never; }[keyof T]]'
// is not assignable to type 'string'. 😭
}
Se microsoft/TypeScript#30728 for mer informasjon.
Så i den overbelastede versjonen lar vi anroperen se begrensningen på key og implementeringen se begrensningen på obj, noe som fungerer bedre for alle:
shouldOnlyAcceptStringValues2(obj, "a"); // error!
// ------------------------------> ~~~
// Argument of type '"a"' is not assignable to parameter of type '"b" | "c"'
shouldOnlyAcceptStringValues2(obj, "b"); // okay
shouldOnlyAcceptStringValues2(obj, "c"); // okay
Nå klager kompilatoren på key i stedet for obj.
Ok, håper det hjelper; lykke til!
Lenke til kode på lekeplassen
