Fermer

novembre 4, 2021

GraphQL sur le frontend (React et Apollo)


Résumé rapide ↬

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.

En savoir plus après le saut ! Continuez à lire ci-dessous ↓

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/clientet cela nous aide à faire des mutations sur les données sur notre serveur.

Habituellement, nous pouvons créer des mutations avec Apollo en utilisant useMutationsmais 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 (
setName(e.target.value)} obligatoire />
{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 (
setName(e.target.value)} obligatoire />
{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 useQuerynous 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 (
setName(e.target.value)} obligatoire />
{petsList}
); } export default OptimisticPets;

Lorsque le code ci-dessus appelle onSubmitle 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 optimisticResponsetout 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 truece champ est résolu et ajouté, et s'il est falseil 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.

Smashing Editorial(vf, yk, il)




Source link