typescript - Kontrollera att argumenten till en funktion är nycklarna för en korrekt typ i en typparameter
Jag försöker bygga en fin, Rails-liknande formulärbyggare i React med TypeScript. Jag är fascinerad av de möjliga kompileringstidskontrollerna, och jag är nyfiken på att se om det finns någon specifik jag kan implementera. Jag är inte säker på hur jag ska formulera denna fråga abstrakt, men jag tror att ett litet exempel ändå kommer att illustrera det bättre.
Låt oss säga att jag har en generell Input-typ som är ett objekt med vissa egenskaper. Jag skulle vilja ha en funktion, numericField, som tar ett inmatningsobjekt och nyckeln K där Input[K] är av typen number. Om jag skickar en felstavad nyckel eller en nyckel av typen string, vill jag få ett kompilatorfel.
Till exempel:
interface Person {
name: string
age: number
}
Jag skulle vilja få följande:
decimalField<Person>({input: person, key: 'age'}) // works
decimalField<Person>({input: person, key: 'agge'}) // compiler error
decimalField<Person>({input: person, key: 'name'}) // compiler error
Jag har lyckats göra det med följande typ:
export type PropertiesOfSubtype<T, P> = {
[K in keyof T]-?: Exclude<T[K], undefined | null> extends P ? K : never
}[keyof T]
Om jag definierar decimalField som:
function decimalField<Input>(props: {input: Input, key: PropertiesOfType<Input, number>})
..det fungerar på ett sätt. Men det finns ett problem.
Jag vill att TypeScript ska veta att input[key] returnerar ett nummer. För närvarande gör det inte det. Jag tror att det kan finnas ett sätt att skriva om det så att det kan typkontrolleras eftersom TS vet att det kommer att returnera en sträng.
Jag antar att min fråga är: finns det ett bättre sätt att göra det här?
P.P.: Här är en lekplats med exemplet, med vad mitt nästa steg är - ett valfritt/obligatoriskt argument beroende på om det finns en fördefinierad etikett eller för fältet.
Svar #1
När du säger att du vill att input[key] ska förstås som ett number, menar du inuti implementeringen av decimalField(), eller hur? Inuti implementeringen är Input en ospecificerad generisk typparameter. När typer beror på ospecificerade generiska typparametrar kommer kompilatorn ha lättare att förstå att input[key] är av typen number om input-typen uttryckligen begränsas till Record<typeof key, number>. Teoretiskt sett skulle kompilatorn kunna lista ut det själv, men i praktiken utför den inte sådan högre ordningens analys med ospecificerade generika.
Du skulle möjligen kunna skriva det på det här sättet:
type PropertyHaver<T, P> = { [K in PropertiesOfSubtype<T, P>]: P };
Och sedan kan decimalField:s generiska typparameter begränsas på detta sätt:
function decimalField<Input extends PropertyHaver<Input, number>>(
props: { input: Input, key: PropertiesOfSubtype<Input, number> }) {
const value: number = props.input[props.key]
return "whatever"
}
Implementeringen förstår att input[key] nu är ett number. (Observera, det är egentligen props.input[props.number].)
Okej, hoppas det hjälper; lycka till!
Playground-länk till kod
