Typescript er det muligt at udlede typer baseret på argumentenum?
Lad os sige, at jeg har en funktion, der har 3 argumenter. Det første argument er en enum, som er defineret af mig, det andet argument er et objekt, der har formen baseret på værdien, der er sendt af enumen. Det tredje argument er bare en streng.
Se på denne funktion:
async callApi<Response = any, Input = any>(
op: ApiOperations,
input: Input,
functionName: string
): Promise<Response> {
return this.lambda.invoke<Response>(functionName, {
op,
input
});
}
Du kan se, at jeg erklærer denne funktion som generisk og modtager to typer og sætter standarden til any, og det virker. Men i dette tilfælde, hver gang jeg vil kalde denne funktion, skal jeg manuelt angive input- og responstyperne. Sagen er, at jeg ved, at for hver værdi fra min ApiOperations-enum har jeg kun én inputtype og én responstype.
Så mit spørgsmål er, er der nogen måde, TypeScript kan udlede typerne baseret på enum-værdien?
Et eksempel på at kalde denne funktion er:
async getChatRooms({ numberResults, siteId, startIndex }: GetChatRoomsInput): Promise<ChatRoom[]> {
return this.api.callApi<ChatRoom[], GetChatRoomsInput>(ApiOperations.GetChatRooms, {
siteId,
numberResults,
startIndex
}, 'getChatRooms');
}
Dette fungerer fint, men den måde, jeg ønsker at gøre det på, er:
async getChatRooms({ numberResults, siteId, startIndex }: GetChatRoomsInput): Promise<ChatRoom[]> {
return this.api.callApi(ApiOperations.GetChatRooms, {
siteId,
numberResults,
startIndex
}, 'getChatRooms');
}
Og for denne sag ville TypeScript kunne fortælle mig, at inputtet har typen GetChatRoomsInput og ChatRoom[] som responstype.
Svar #1
Du har bare brug for en opslagstype, der kortlægger enum-værdier til input-/responstyper. For eksempel:
enum ApiOperations {
A,
B,
}
interface ApiOperationsLookup {
[ApiOperations.A]: {
input: { a: number }
response: { a: string }
}
[ApiOperations.B]: {
input: { b: number }
response: { b: string }
}
}
Her er ApiOperationsLookup en type, der har nøgle navnene på ApiOperations kortlagt til specifikke input- og responstyper.
Du kan hente inputtypen med noget som:
type Test = ApiOperationsLookup[ApiOperations.A]['input']
// { a: number }
Nu kan du få callApi til at se sådan ud:
async callApi<T extends ApiOperations>(
op: T,
input: ApiOperationsLookup[T]['input'],
functionName: string
): Promise<ApiOperationsLookup[T]['response']> {
//...
}
Her er den generiske parameter T en værdi fra ApiOperations, og derefter hentes input og returtypen fra opslagstabellen for den operation.
Dette virker nu, som jeg tror, du forventer:
const aResponse = await instance.callApi(ApiOperations.A, { a: 123 }, 'aFuncName')
// { a: string }
const aBadResponse = await instance.callApi(ApiOperations.A, { b: 123 }, 'aFuncName')
// type error on second argument
const bResponse = await instance.callApi(ApiOperations.B, { b: 123 }, 'aFuncName')
// { b: string }
Legeplads
En anden mulighed er at springe opslagstypen over og i stedet bruge overloads, hvor du opretter en funktionssignatur for hvert medlem af enum'en:
// signature for each enum member
async callApi(op: ApiOperations.A, input: { a: number }, functionName: string): Promise<{ a: string }>
async callApi(op: ApiOperations.B, input: { b: number }, functionName: string): Promise<{ b: string }>
// implementation
async callApi(
op: ApiOperations,
input: unknown,
functionName: string
): Promise<unknown> {
//...
}
Jeg synes, det er mere grimt og sværere at vedligeholde personligt, men det er et spørgsmål om mening.
Legeplads
