GraphQL sur le frontend (React et Apollo)

Au cours de la dernière décennie, des technologies comme GraphQL ont changé la façon dont nous créons des applications Web et comment elles communiquent entre elles. GraphQL offre certains avantages par rapport aux API REST – découvrons ce qu'ils sont.
L'un des principaux avantages de GraphQL est la capacité du client à demander au serveur ce dont il a besoin et à recevoir ces données de manière précise et prévisible. Sans trop d'effort, on peut facilement extraire des données imbriquées en ajoutant simplement plus de propriétés à nos requêtes au lieu d'ajouter plusieurs points de terminaison. Cela évite des problèmes tels que la récupération excessive qui peuvent affecter les performances.
Habituellement, pour gérer GraphQL côté client, nous utilisons le client Apollo. Il permet aux développeurs de définir, gérer et rendre disponibles des requêtes/mutations dans notre application. Il peut également servir d'outil de gestion d'état avec votre application côté client.
Dans cet article, nous allons apprendre à gérer les mises à jour en temps réel côté client à l'aide de GraphQL. Nous allons apprendre à le faire avec les fonctionnalités de GraphQL telles que la mise à jour du cache, les abonnements et l'interface utilisateur optimiste. Nous aborderons également la façon d'utiliser Apollo comme outil de gestion d'état, remplaçant éventuellement le redux. De plus, nous verrons comment créer des requêtes GraphQL utilisables avec des fragments et comment utiliser les directives Apollo pour écrire des requêtes plus complexes.
Installation
Avant de commencer, passons simplement à l'installation et à la configuration de notre projet. Entrons directement dans le code. Pour créer une application React, assurez-vous que Node.js est installé sur votre ordinateur. Si vous n'avez jamais créé d'application React auparavant, vous pouvez vérifier si Node.js est installé en tapant ce qui suit dans votre terminal :
node -v
Si ce n'est pas le cas, accédez simplement à Node.js site Web pour télécharger la dernière version.
Une fois cela fait, nous pouvons commencer avec notre application React en exécutant cette commande :
npx create-react-app react-graphql
Une fois, c'est fait, nous allons installer Apollo en utilisant :
nom i @apollo/client
Ensuite, nous naviguons dans notre dossier de projet sur le terminal :
cd react-graphql
Ou mieux, vous pouvez simplement continuez et clonez le repo. Le référentiel contient à la fois le côté client et le serveur, nous avons donc d'autres dépendances nécessaires.
Nous installerions ces dépendances en exécutant :
npm install
Juste avant de commencer, voici le repo contenant le code démontrant tout sous la mise à jour en temps réel sur GraphQL, en utilisant Apollo comme outil de gestion d'état, des fragments et des directives Apollo. Voici également le repo contenant le code démontrant l'abonnement côté client.
Mise à jour en temps réel sur GraphQL
La possibilité de créer une mise à jour en temps réel côté client permet d'améliorer l'expérience utilisateur du site, ce qui rend tout plus fluide. Imaginez simplement une situation où un utilisateur ajoute un nouvel élément en remplissant un formulaire, et cet élément se met à jour instantanément en étant ajouté à la liste des éléments sur la même page. Bien que cette mise à jour en temps réel puisse se synchroniser avec un serveur directement via des abonnements, ou elle peut être manipulée sur le front-end via des choses comme Optimistic UI, ou en utilisant la fonction update
sur le useMutation
. Passons donc à la réalisation technique. Voici le repo contenant le code démontrant tout sous Mise à jour en temps réel sur Graphql, utilisant Apollo comme outil de gestion d'état, Fragments et les directives Apollo.
Mise à jour du cache directement à l'aide de update
sur la useMutation
useMutations
sont importés directement de la bibliothèque @apollo/client
et cela nous aide à faire des mutations sur les données sur notre serveur.
Habituellement, nous pouvons créer des mutations avec Apollo en utilisant useMutations
mais au-delà, nous utiliserons la fonction update
pour mettre à jour notre cache apollo-client directement via useMutation
.
Dans cet exemple ci-dessous, nous envoyons des requêtes au serveur pour obtenir une liste d'animaux à l'aide de useQuery
et effectuer une mutation en ayant un formulaire pour ajouter plus d'animaux à notre serveur en utilisant useMutation
. Le problème que nous aurons est que lorsqu'un nouvel animal de compagnie est ajouté au serveur, il n'est pas ajouté à la liste des animaux de compagnie (sur le navigateur) immédiatement, à moins que la page ne soit actualisée. Cela rend l'expérience utilisateur de cette section de l'application interrompue, d'autant plus que la liste des animaux de compagnie et le formulaire se trouvent sur la même page.
import React, { useState } from "react" ;
importer gql depuis "graphql-tag" ;
import { useQuery, useMutation } depuis "@apollo/client" ;
importer le chargeur de "../components/Loader" ;
importer PetSection depuis "../components/PetSection" ;
//ALL_PETS utilise gql de @apollo/client pour nous permettre d'envoyer des requêtes imbriquées
const ALL_PETS = gql`
requête AllPets {
animaux domestiques {
identifiant
Nom
taper
img
}
}
`;
// NEW_PET utilise gql de @apollo/client pour créer des mutations
const NEW_PET = gql`
mutation CreateAPet($newPet: NewPetInput!) {
ajoutéPet(entrée : $newPet) {
identifiant
Nom
taper
img
}
}
`;
function Animaux () {
const initialCount = 0;
const [count, setCount] = useState(initialCount);
const pets = useQuery(ALL_PETS);
const [createPet, newPet] = useMutation(NEW_PET);
const [name, setName] = useState("");
type const = 'CHIEN';
const onSubmit = (entrée) => {
createPet({
variables : { newPet : input },
});
} ;
// cette fonction déclenche l'action de soumission en appelant la fonction onSubmit au-dessus
const soumettre = (e) => {
e.preventDefault();
onSubmit({ nom, type });
} ;
//Si les données sont en cours de chargement, nous affichons le composant à la place
if (pets.loading || newPet.loading) {
retour ;
}
// parcourt les données des animaux afin d'obtenir chaque animal et de les afficher avec des accessoires à l'aide du composant
const petsList = pets.data.pets.map((pet) => (
));
revenir (
{petsList}
);
}
exporter les animaux par défaut ;
L'utilisation de la fonction update
dans le hook useMutation
nous permet de mettre directement à jour notre cache en lisant et en écrivant notre ALL_PETS
. Dès que nous appuyons sur le bouton d'envoi, les données sont ajoutées à la liste des animaux de compagnie dans le cache en modifiant ALL_PETS
. Cela nous permet de mettre à jour notre cache côté client immédiatement avec des données cohérentes.
import React, { useState } from "react";
importer gql depuis "graphql-tag" ;
import { useQuery, useMutation } depuis "@apollo/client" ;
importer le chargeur de "../components/Loader" ;
importer PetSection depuis "../components/PetSection" ;
//ALL_PETS utilise gql de @apollo/client pour nous permettre d'envoyer des requêtes imbriquées
const ALL_PETS = gql`
requête AllPets {
animaux domestiques {
identifiant
Nom
taper
img
}
}
`;
// NEW_PET utilise gql de @apollo/client pour créer des mutations
const NEW_PET = gql`
mutation CreateAPet($newPet: NewPetInput!) {
ajoutéPet(entrée : $newPet) {
identifiant
Nom
taper
img
}
}
`;
function LesAnimaux() {
const initialCount = 0;
const [count, setCount] = useState(initialCount);
const pets = useQuery(ALL_PETS);
//Nous utilisons ensuite useMutation et update() pour mettre à jour notre ALL_PET
const [createPet, newPet] = useMutation(NEW_PET, {
update(cache, {données : {addPet}}) {
const allPets = cache.readQuery({requête : ALL_PETS})
cache.writeQuery({
requête : ALL_PETS,
données : {animaux : [addedPet, ...allPets.pets]}
})
}
});
const [name, setName] = useState("");
type const = 'CHIEN';
const onSubmit = (entrée) => {
createPet({
variables : { newPet : input },
});
} ;
// Gère la soumission des animaux de compagnie qui déclenche finalement createPet via onSumit
const soumettre = (e) => {
e.preventDefault();
onSubmit({ nom, type });
} ;
//Si les données sont en cours de chargement, nous affichons le composant à la place
if (pets.loading || newPet.loading) {
retour ;
}
// parcourt les données des animaux afin d'obtenir chaque animal et de les afficher avec des accessoires à l'aide du composant
const petsList = pets.data.pets.map((pet) => (
));
revenir (
{petsList}
);
}
exporter les animaux de compagnie par défaut ;
Abonnements dans GraphQL
Basé sur les fonctionnalités, l'abonnement dans GraphQL est similaire aux requêtes. La principale différence est que, bien que les requêtes ne soient effectuées qu'une seule fois, les abonnements sont connectés au serveur et se mettent automatiquement à jour en cas de modification de cet abonnement particulier. Voici le repo contenant le code démontrant l'abonnement côté client.
Tout d'abord, nous devons installer :
npm install subscriptions-transport-ws
Ensuite, nous allons à notre index.js
pour l'importer et l'utiliser.
importer { WebSocketLink } depuis "@apollo/client/link/ws" ;
//configuration de nos sockets Web à l'aide de WebSocketLink
const link = new WebSocketLink({
uri : `ws://localhost:4000/`,
choix : {
reconnecter : vrai,
},
});
client const = new ApolloClient({
relier,
uri : "http://localhost:4000",
cache : nouveau InMemoryCache(),
});
Remarque : uri
dans le bloc de code directement au-dessus est pour notre point de terminaison.
Ensuite, nous entrons dans notre composant et au lieu de la requête comme ci-dessus, nous utiliserons cet abonnement à la place :
import { useMutation, useSubscription } de "@apollo/client" ;
//initier notre abonnement côté client
const ALL_PETS = gql`
abonnement AllPets {
animaux domestiques {
identifiant
Nom
taper
img
}
}
`;
Et au lieu d'utiliser useQuery
nous accéderions à nos données en utilisant useSubscription
.
const getMessages = useSubscription(ALL_PETS);
Optimistic UI
Optimistic UI est un peu différent dans le sens où il ne se synchronise pas avec le serveur, comme un abonnement. Lorsque nous effectuons une mutation, au lieu d'attendre une autre demande du serveur, il utilise automatiquement les données déjà saisies pour mettre à jour immédiatement la liste des animaux de compagnie. Ensuite, une fois les données d'origine du serveur arrivées, elles remplaceront la réponse optimiste. Ceci est également différent de « Mettre à jour le cache directement à l'aide de la fonction update
sur useMutation
", même si nous allons toujours mettre à jour le cache dans ce processus.
import React, { useState } de "react" ;
importer gql depuis "graphql-tag" ;
importer { useQuery, useMutation } depuis "@apollo/client" ;
importer le chargeur de "./Loader" ;
importer PetSection depuis "./PetSection" ;
//Nous utilisons ALL_PET pour envoyer nos requêtes imbriquées au serveur
const ALL_PETS = gql`
requête AllPets {
animaux domestiques {
identifiant
Nom
taper
img
}
}
`;
//Nous utilisons NEW_PET pour gérer nos mutations
const NEW_PET = gql`
mutation CreateAPet($newPet: NewPetInput!) {
addPet(entrée : $newPet) {
identifiant
Nom
taper
img
}
}
`;
function Animaux optimistes () {
//Nous utilisons useQuery pour gérer la réponse ALL_PETS et l'affecter aux animaux de compagnie
const pets = useQuery(ALL_PETS);
//Nous utilisons useMutation pour gérer les mutations et mettre à jour ALL_PETS.
const [createPet, newPet] = useMutation(NEW_PET
, {
update(cache, {données : {addPet}}) {
const allPets = cache.readQuery({requête : ALL_PETS})
cache.writeQuery({
requête : ALL_PETS,
données : {animaux : [addPet, ...allPets.pets]}
})
}
});;
const [name, setName] = useState("");
type const = 'CHIEN';
// Gère la mutation et crée la réponse optimiste
const onSubmit = (entrée) => {
createPet({
variables : { newPet : input },
optimisteRéponse : {
__typename : 'Mutation',
addPet : {
__typename : 'Animal de compagnie',
id : Math.floor(Math.random() * 1000000) + '',
tapez : " CAT ",
nom : entrée.nom,
img : 'https://via.placeholder.com/300',
}
}
});
} ;
//Voici notre soumission déclenche la fonction onSubmit
const soumettre = (e) => {
e.preventDefault();
onSubmit({ nom, type });
} ;
// renvoie le chargement du composant lorsque les données sont toujours en cours de chargement
if (animaux de compagnie.chargement ) {
retour ;
}
// parcourt les animaux et les affiche dans le composant PetSection
const petsList = pets.data.pets.map((pet) => (
));
revenir (
{petsList}
);
}
export default OptimisticPets;
Lorsque le code ci-dessus appelle onSubmit
le cache du client Apollo stocke un objet addPet
avec les valeurs de champ spécifiées dans optimisticResponse
. Cependant, il n'écrase pas les principaux pets(ALL_PETS)
mis en cache avec le même identifiant de cache. Au lieu de cela, il stocke une version distincte et optimiste de l'objet. Cela garantit que nos données mises en cache restent exactes si notre optimisticResponse
est erronée.
Apollo Client notifie toutes les requêtes actives qui incluent les pets(ALL_PETS)
modifiés. Ces requêtes sont automatiquement mises à jour et leurs composants associés sont réaffichés pour afficher nos données optimistes. Cela ne nécessite aucune requête réseau, il s'affiche donc instantanément pour l'utilisateur.
Finalement, notre serveur répond à la mutation réelle pour obtenir le bon objet addPet
. Ensuite, le cache du client Apollo rejette notre version optimiste de l'objet addPet
. Il écrase également la version mise en cache avec les valeurs renvoyées par le serveur.
Apollo Client notifie immédiatement toutes les requêtes concernées à nouveau. Les composants concernés s'affichent à nouveau, mais si la réponse du serveur correspond à notre optimisticResponse
tout le processus est invisible pour l'utilisateur. dérange. Fait intéressant, Apollo peut également servir d'outil de gestion pour notre état local. Similaire à ce que nous avons fait avec notre API.
Schémas et résolveurs côté client
Pour y parvenir, nous devrons écrire des schémas côté client pour définir le type de données que nous voulons et comment nous voulons qu'il soit structuré. Pour ce faire, nous allons créer Client.js
où nous définirons les schémas et les résolveurs, après quoi nous le rendrons globalement accessible dans notre projet avec le client Apollo.
Pour cela exemple, je vais étendre le type User
qui existe déjà pour ajouter height
en tant qu'entier. Les résolveurs sont également ajoutés pour remplir le champ height
dans notre schéma.
import { ApolloClient } depuis 'apollo-client'
importer { InMemoryCache } depuis 'apollo-cache-inmemory'
importer { ApolloLink } depuis 'apollo-link'
importer { HttpLink } depuis 'apollo-link-http'
importer { setContext } depuis 'apollo-link-context'
importer gql depuis 'graphql-tag'
//Extension du type d'utilisateur
const typeDefs = gql`
type d'extension utilisateur {
hauteur: Int
}
`
//Déclarer notre hauteur à l'intérieur de nos résolveurs côté client
résolveurs const = {
Utilisateur : {
la taille() {
retour 35
}
}
}
cache const = nouveau InMemoryCache()
const http = nouveau HttpLink({
uri : 'http://localhost:4000/'
})
lien const = ApolloLink.from([
http
])
client const = new ApolloClient({
relier,
cache,
typeDefs,
résolveurs
})
exporter le client par défaut
client.js
Nous pouvons ensuite importer le client
dans notre index.js
:
import client de "./client"
importer {
Fournisseur Apollo,
} de "@apollo/client" ;
//importation de notre fichier client.js dans ApolloProvider
ReactDOM.render(
,
document.getElementById("root")
);
index.js
Dans le composant, il l'utilisera comme ceci. Nous ajoutons @client
pour indiquer que la requête provient du côté client et qu'elle ne doit pas essayer de l'extraire du serveur.
const ALL_PETS = gql`
requête AllPets {
animaux domestiques {
identifiant
Nom
taper
img
propriétaire {
identifiant
hauteur @client
}
}
}
`;
Nous extrayons donc les données du serveur et du client dans la même requête, et elles seront accessibles via le hook useQuery
.
Fragments-Création de requêtes réutilisables[19659006]Parfois, nous pouvons avoir besoin d'extraire la même requête dans différents composants. Ainsi, au lieu de la coder en dur plusieurs fois, nous attribuons cette requête à une sorte de variable et utilisons cette variable à la place.
Dans notre composant, nous définissons simplement le fragment comme PetFields
sur Pet
(qui est le Type). De cette façon, nous pouvons simplement l'utiliser à la fois dans notre requête
et mutation
.
const DUPLICATE_FIELD = gql`
fragment PetFields sur Pet {
identifiant
Nom
taper
img
}
`
const ALL_PETS = gql`
requête AllPets {
animaux domestiques {
...Champs pour animaux de compagnie
}
}
${DUPLICATE_FIELD}
`;
const NEW_PET = gql`
mutation CreateAPet($newPet: NewPetInput!) {
addPet(entrée : $newPet) {
...Champs pour animaux de compagnie
}
}
${DUPLICATE_FIELD}
`;
Directives Apollo
Lors de la création de requêtes, nous pouvons souhaiter avoir des conditions qui suppriment ou incluent un champ ou un fragment si une condition particulière est remplie ou non. Les directives par défaut incluent :
@skip
: indique qu'un champ/fragment doit être ignoré si une condition est remplie.
const ALL_PETS = gql`
requête AllPets($nom : booléen !){
animaux domestiques {
identifiant
nom @skip : (si : $nom)
taper
img
}
}
`;
Ici $name
est un booléen qui est ajouté en tant que variable lorsque nous appelons cette requête. Qui est ensuite utilisé avec @skip
pour déterminer quand afficher le champ name
. Si vrai, il saute, et si faux, il résout ce champ.
@includes
fonctionne également de la même manière. Si la condition est true
ce champ est résolu et ajouté, et s'il est false
il n'est pas résolu.
Nous avons également @deprecated
qui peut être utilisé dans les schémas
pour retirer des champs, où vous pouvez même ajouter des raisons.
Nous avons également des bibliothèques qui nous permettent d'ajouter encore plus de directives, elles pourraient s'avérer utiles lors de la construction trucs compliqués avec GraphQL.
Trucs et astuces avec l'utilisation de GraphQL Lodash dans vos requêtes
GraphQL Lodash est une bibliothèque qui peut nous aider à effectuer une requête de manière plus efficace, plus comme une forme avancée de les directives Apollo.
Cela peut vous aider à interroger votre serveur d'une manière qui renvoie les données de manière plus nette et compacte. Par exemple, vous interrogez le titre
de films
comme ceci :
films {
Titre
}
Et il renvoie le titre
des films sous forme d'objets dans un tableau.
"films": [
{
"title" : "Prremier English"
},
{
"title" : "There was a country"
},
{
"title" : "Fast and Furious"
}
{
"title" : "Beauty and the beast"
}
]
Mais, lorsque nous utilisons la directive map
de lodash , quand peut en quelque sorte parcourir le tableau films pour avoir un seul tableau avec tous les titres en tant qu'enfants directs. Nous enverrions une requête à notre serveur qui ressemble à ceci :
films @_(map: "title") {
Titre
}
Vous obtiendrez cette réponse que l'on pourrait considérer comme relativement plus nette que la précédente.
"films": [
"Premier English",
"There was a country",
"Fast and Furious",
"Beauty and the beast"
]
Une autre qui s'avère utile est la directive is keyby
. Vous pouvez envoyer une requête simple comme celle-ci :
people {
Nom
âge
genre
}
Réponse :
"people" : [
{
"name": "James Walker",
"age": "19",
"gender": "male"
},
{
"name": "Alexa Walker",
"age": "19",
"gender": "female"
},
]
Utilisons la directive @_keyup
dans notre requête :
people @_(keyBy: "name") {
Nom
âge
genre
}
La réponse ressemblera à ceci :
"people" : [
"James Walker" : {
"name": "James Walker",
"age": "19",
"gender": "male"
}
"Alexa Walker" : {
"name": "Alexa Walker",
"age": "19",
"gender": "female"
}
]
Donc, dans ce cas, chaque réponse a une clé, c'est le nom
de la personne.[19659105]Conclusion
Dans cet article, nous avons abordé des sujets avancés pour réaliser une mise à jour en temps réel des données à l'aide de la fonction update()
de l'abonnement et de l'interface utilisateur optimiste. Tout cela pour améliorer l'expérience utilisateur.
Nous avons également abordé l'utilisation de GraphQL pour gérer l'état côté client et la création de requêtes réutilisables avec des fragments GrahQL. Ce dernier nous permet d'utiliser les mêmes requêtes dans différents composants où cela est nécessaire sans avoir à répéter tout le processus à chaque fois. meilleure façon. Vous pouvez également consulter le le tutoriel de Scott Moss si vous cherchez à couvrir Graphql et à réagir à partir de zéro.

Source link