Hvordan bruke graphql fetch i TypeScript
Jeg så dette spørsmålet som er veldig nær det jeg prøver å oppnå, men det fungerte ikke helt for meg fordi endepunktet er et GraphQL-endepunkt og det finnes en annen innbyrdes egenskap med navnet på forespørselen. For eksempel, hvis min forespørsel er:
const query = `query Query($age: Int!){
users(age: $age) {
name
birthday
}
}`;
Da er objektet som hentes fra svaret lenket ovenfor data.data.users, der den siste egenskapen kommer fra navnet på GraphQL-spørringen selv. Jeg klarte å endre koden fra lenken ovenfor til følgende:
function graphQLFetch<T>(url: string, query: string, variables = {}): Promise<T> {
return fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, variables }),
}).then((response) => {
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json();
})
.then((responseJson) => responseJson.data[Object.keys(responseJson.data)[0]] as Promise<T>);
}
...som fungerer når jeg bare sender en enkelt forespørsel til GraphQL-endepunktet. Hvordan kan jeg generalisere slik at det vil fungere for et hvilket som helst antall forespørsler? For eksempel, hva om jeg vet at forespørselen min vil returnere User[] og Post[], som en tuppel, på grunn av følgende GraphQL-forespørsel:
const query = `query Query($age: Int!, $username: String!){
users(age: $age) {
name
birthday
}
posts(username: $username) {
date
}
}`;
}
Da vil jeg gjerne at noe som følgende skal fungere:
const myData = graphQLFetch<[User[], Post[]]>(url, query, variables);
Er noe sånt mulig?
Svar #1
Akkurat nå ville jeg sagt at problemet ditt er dette...
responseJson.data[Object.keys(responseJson.data)[0]]
Det vil bare noen gang returnere den første verdien fra data.
Jeg vil likevel fraråde å bruke tupler for dette. I stedet, returner bare data-objektet typet til forventet respons.
La oss starte med en generell objekt-type for å representere GraphQL data
type GraphQlData = { [key: string]: any, [index: number]: never };
Dette beskriver den mest generiske formen data kan ta. Det er i prinsippet et vanlig objekt med strengnøkler. never på numeriske indekser hindrer det i å være et array.
Deretter la oss beskrive GraphQL-responsformen
interface GraphQlResponse<T extends GraphQlData> {
data: T;
errors?: Array<{ message: string }>;
}
Dette representerer respons-JSON du får fra GraphQL, inkludert den tidligere GraphQlData-typen eller noe som spesialiserer seg på det. For eksempel, du kan spesifisere en bestemt responstype som...
type UsersAndPostsResponse = GraphQlResponse<{ users: Users[], posts: Posts[] }>;
Her er { users: Users[], posts: Posts[] } en mer spesialisert versjon av GraphQlData med nøkler begrenset til users og posts og spesifikke verdityper.
Til slutt, definer funksjonen, og innarbeid den samme generiske data-typen
async function graphQLFetch<T extends GraphQlData>(
url: string,
query: string,
variables = {}
): Promise<T> {
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, variables }),
});
if (!res.ok) {
throw new Error(`${res.status}: ${res.statusText}`);
}
// cast the response JSON to GraphQlResponse with the supplied data type `T`
const graphQlRes: GraphQlResponse<T> = await res.json();
if (graphQlRes.errors) {
throw new Error(graphQlRes.errors.map((err) => err.message).join("\n")); // you might want to create a custom Error class
}
return graphQlRes.data;
}
Da kan du lage forespørselen din slik
const { users, posts } = await graphQLFetch<{ users: User[]; posts: Post[] }>(
url,
query
);
Ellers vil du ønske å hente alle data verdiene, og hvis det er mer enn én, returner tuplen din i stedet for en enkelt post.
For å støtte en slik generisk returtype, må du spesifisere T som en union av enkeltype eller array-type.
Merk: Det er en iboende risiko her for at dataverdiene ikke er i den rekkefølgen du spesifiserer. Det er mye bedre å bruke verdiene etter nøkkel.
// Note there's no generic type now
interface GraphQlResponse {
data: GraphQlData;
errors?: Array<{ message: string }>;
}
async function graphQLFetch<T extends any | Array<any>>(
url: string,
query: string,
variables = {}
): Promise<T> {
const res = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, variables }),
});
if (!res.ok) {
throw new Error(`${res.status}: ${res.statusText}`);
}
const graphQlRes: GraphQlResponse = await res.json();
if (graphQlRes.errors) {
throw new Error(graphQlRes.errors.map((err) => err.message).join("\n"));
}
const values = Object.values(graphQlRes.data);
if (values.length === 1) {
return values[0] as T;
}
return values as T;
}
