Fermer

août 30, 2021

GraphQL et relais dans le frontal


Lors de la création d'une application React qui utilisera une API GraphQL, vous avez besoin d'un client qui peut vous aider à évoluer tout en maintenant les performances. Explorons Relay.

L'écosystème GraphQL s'est considérablement développé ces dernières années. Il y a toujours du nouveau contenu publié, de nouveaux tutoriels écrits, de nouvelles bibliothèques créées. Dans l'ensemble, nous pouvons dire que l'utilisation de GraphQL devient populaire.

GraphQL peut être puissant, en particulier pour les applications React. Lors de la création d'une nouvelle application React qui va utiliser une API GraphQL, avoir un bon client GraphQL qui peut aider à faire évoluer et maintenir l'application performante en même temps fait une grande différence.

Créé par Facebook, Relay est un très puissant et framework JavaScript robuste pour travailler de manière déclarative avec GraphQL et créer des applications React basées sur les données. Il a été conçu pour être très performant et hautement évolutif, avec l'évolutivité en tête. Il possède de nombreuses fonctionnalités, telles que la cohérence des données, les mises à jour optimistes, la sécurité des types, l'exécution optimisée, etc.

Relay

Nous allons utiliser la dernière version de Relay. Il prend en charge les crochets React et il est très puissant et facile à utiliser.

Nous allons au nouveau projet create-react-app :

npx create-react-app graphql-relay-example 

Pour commencer à utiliser Relay, nous devons installer quelques dépendances.

yarn add @chakra-ui/icons @chakra-ui/layout @chakra-ui/react @emotion/react babel-plugin-relay framer-motion isomorphic-fetch relay-hooks relay-runtime

Nous devons maintenant installer quelques dépendances de développement.

yarn add --dev @types/isomorphic-fetch @types/react-relay @types/relay-runtime get-graphql-schema graphql react-relay relay-compiler relay-compiler-language-typescript

Nous avons maintenant installé toutes les dépendances dont nous allons avoir besoin. Maintenant, dans notre fichier package.json, ajoutons deux nouveaux scripts :

"get-graphql-schema": "get-graphql-schema https://podhouse-server.herokuapp.com/ graphql > schema.graphql --graphql",
"relay": "relay-compiler --src ./src --schema ./schema.graphql --language typescript"

Le script get-graphql-schema sert à générer notre fichier schema.graphql à partir de notre schéma d'API GraphQL. Le script Relay sert à convertir nos littéraux GraphQL en fichiers générés.

Maintenant, dans notre dossier src, nous allons créer un nouveau fichier appelé relay. Dans ce fichier, nous allons créer notre environnement Relay.

import {
  Environnement,
  Réseau,
  Source d'enregistrement,
  Magasin,
  Paramètres de requête,
  Variables
} de "relay-runtime";
import récupérer de "isomorphic-fetch";

fonction[19659032]fetchQuery(operation: RequestParameters, variables: Variables) {
  return fetch ('https://podhouse-server.herokuapp.com/graphql', {
    méthode: "POST",
    en-têtes : {
      Accepter: "application/json",
      "Content-type": "application/json",
    },
    body: JSON.stringify({
      requête : opération.texte,
      variables,
    }),
  }).puis((réponse :[19659025]tout) => {
    retour réponse.json()
  })[19659074]};

const network = Network.create(fetchQuery);

const  env = nouveau Environnement({
  réseau,
  magasin: nouveau Store(nouveau RecordSource(), {
    gcReleaseBufferSize: 10,
  }),
});

export default env;

L'API que nous utilisons ici est une API de podcast. Après avoir créé notre environnement Relay, nous allons le passer dans notre fichier d'index. Nous devons importer le RelayEnvironmentProvider et transmettre notre environnement en tant qu'environnement.

Le RelayEnvironmentProvider est utilisé pour définir Relay sur un contexte React. La recommandation ici est de toujours utiliser une seule instance de ces composants, à la racine de votre application.

import React from 'react';
import ReactDOM de 'react-dom';
import { RelayEnvironmentProvider } de "react-relay";[19659028]import { ChakraProvider } de "@chakra-ui/react";
import App de ' ./App';
import reportWebVitals from './reportWebVitals';
import Environnement from "./ relais";
ReactDOM.render(
<RelayEnvironmentProvider environment={Environment}>
<Chakra >
<React.StrictMode>
<App />
</React.StrictMode>
</ChakraProvider>
</RelayEnvironmentProvider
>[196590
document.getElementById('root')
);

Nous avons réussi à configurer Relay dans notre application. Nous allons maintenant en savoir plus sur son utilisation pratique.

Query

Le useLazyLoadQuery est le crochet le plus simple pour interroger quelque chose avec Relay. Tout ce que vous avez à faire est de passer une requête GraphQL en utilisant le modèle et les variables graphql (au cas où cela serait nécessaire).

import React from 'react';
import[19659024]{ useLazyLoadQuery } from "react-relay";
;
import { PodcastQuery }  from "./__generated__/PodcastQuery.graphql";

const requête = graphql`
  requête PodcastQuery($_id : ID !) {
    podcast(_id : $_id) {
      identifiant
      _identifiant
      Nom
      auteur
      la description
      site Internet
      rss
      image
    }
  }
`;

const App = () => {
  const données = useLazyLoadQuery<PodcastQuery>(query, { id: 4 }[19659015 { fetchPolicy: 'store-or-network' });
  return <h1 >{données.podcast?.nom}</h1>[19659015];
}

Le hook useLazyLoadQuery présente quelques différences par rapport au QueryRenderer, mais l'une des plus importantes est la nécessité d'utiliser React Suspense pour afficher les états de chargement pendant le chargement des données. Une autre grande différence est que vous ne devez pas transmettre votre environnement Relay à useLazyLoadQuery comme l'a fait QueryRenderer.

Selon la documentation Relay, le hook useLazyLoadQuery peut déclencher plusieurs allers-retours inutiles. C'est pourquoi il est recommandé d'utiliser le hook usePreloadedQuery à la place, qui est le hook que nous allons utiliser. Comprenons maintenant comment fonctionne le hook usePreloadedQuery dans la pratique.

Créons un nouveau dossier appelé composants et créons un nouveau dossier appelé PodcastItem. Dans ce nouveau dossier, nous allons créer un nouveau fichier appelé PodcastItem. Nous utiliserons ce fichier pour le rendu de chaque podcast que nous récupérons à partir de notre API.

Voici à quoi ressemblera notre PodcastItem :

import React from "react" ;
import { Link as ReactRouterLink } from "react-router-dom";
import  { Grille, GridItem, Image, Titre, Texte } de "@ chakra-ui/react";

interface Accessoires {
  nœud: {
    id en lecture seule: chaîne;
    readonly _id: string;
    nom en lecture seule: chaîne;
    description en lecture seule: chaîne;
    readonly image: string;
  } | null;
};

const PodcastItem =  ({ node }: Props) => (
  <Grid
    h="auto"
    templateRows="repeat(2, max-content)"
    templateColumns="80px 1fr"
    gap={4}
  >
    <GridItem w="80px" h=[19659038]"80px" rowSpan={2} colSpan={1}>[19659262]<ReactRouterLink to={{ pathname: `/podcast/${node? ._id}`, état: { _id: nœud ?._id } }}>
        <Image src={node?.image} alt="Podcast image" />
      </ReactRouterLink >
    </GridItem>
     <GridItem colSpan={2}>
      <ReactRouterLink to={{ chemin: `/podcast/${node?._id}` , état: { _id: nœud?._id } }[19659015]}>
        <Taille du titre="md" letterSpacing="-0.03em" curseur=[19659038]"pointeur">
          {node?.nom}
        </En-tête>[19659262]</ReactRouterLink>
    </GridItem>
    <GridItem colSpan={[19659249]2}>
      <Texte>{node?.description}</Texte>
    < /GridItem>
    <GridItem colSpan={4} />
  <[19659135]/Grid>
);

export default PodcastItem;

Maintenant, dans notre dossier src, nous allons créer deux nouveaux fichiers appelés Podcasts et PodcastsResults.

Dans notre fichier Podcasts, nous utiliserons usePreloadedQuery pour interroger une liste de podcasts. Tout d'abord, nous devons importer le useQueryLoader de react-relay.

import { useQueryLoader } from "react-relay" ;`

Nous allons maintenant créer notre requête à l'aide du modèle littéral graphql. Notre requête va s'appeler PodcastsQuery, et nous allons y passer un fragment. Je sais que vous vous demandez « Pourquoi transmettre un fragment », n'est-ce pas ? Cela aura du sens plus tard, croyez-moi.

const podcastsQuery = graphql`
  requête PodcastsQuery {
    ...PodcastsRésultats_podcasts
  }
`;

Maintenant dans notre composant, nous allons utiliser le hook useQueryLoader. Il renvoie trois valeurs : la référence de requête, une fonction loadQuery qui, une fois exécutée, chargera notre requête et une fonction disposeQuery qui, une fois exécutée, définira notre référence de requête sur null.

const [queryReference , loadQuery, disposeQuery] = useQueryLoader<_PodcastsQuery_>(
podcastsRequête
);

Nous allons utiliser notre fonction loadQuery à chaque fois que notre composant est monté, et utiliser le disposeQuery à chaque démontage de notre composant.

useEffect(() =>[19659024]{
loadQuery({});
return () => {
 disposeQuery();
};
}, [loadQuery, disposeQuery][19659015]);

Rappelez-vous que nous devons utiliser React Suspense ? Donc, pour le rendre, nous devons l'envelopper dans un composant Suspense. Nous allons montrer un simple composant de secours juste pour montrer que le contenu est en cours de chargement.

Maintenant, nous allons importer nos composants PodcastsResults et les passer dans le composant Suspense. Nous passerons nos podcastsQuery et queryReference en tant qu'accessoires.

Après tout ce travail, notre fichier Podcasts ressemblera à ceci :

import React, { useEffect Suspense } from "react";
import graphql from "babel-plugin-relay/macro"; 
import { useQueryLoader } from "react-relay";
import { PodcastsQuery }[19659025]from "./__generated__/PodcastsQuery.graphql";
import PodcastsResults from "./PodcastsResults";

const[1945900] = graphql`
  requête PodcastsQuery {
    ...PodcastsRésultats_podcasts
  }
`;

const Podcasts = () => {
  const [queryReference, loadQuery, disposeQuery] = useQueryLoader<PodcastsQuery>(
    podcastsRequête
  );

  useEffect(() => {
    loadQuery({}[19659015]);
    retour () => {
      disposeQuery();
    }[19659015];
  }, [loadQuery, disposeQuery]);

  return (
    < div>
      {queryReference && (
        <Suspense fallback={<h1>Chargement...</h1>}>
          <Résultats des podcasts
            podcastsQuery={podcastsQuery}
            queryReference={queryReference}
          />
        </Suspense>
      )}[19659252]</div>
  )
};

export default Podcasts;

Notre composant Podcasts est maintenant prêt. Nous allons maintenant travailler sur notre composant PodcastsResults et comprendre un peu plus le fonctionnement du hook usePreloadedQuery et découvrir un nouveau hook pour la pagination.

Dans notre fichier PodcastsResults, nous allons utiliser deux différents Crochets de relais. Mais d'abord, nous devons importer quelques éléments :

import React, { useCallback } from "react";[19659028]import { VStack, StackDivider, Button } from "@chakra-ui/react"
import  graphql from "babel-plugin-relay/macro";
import { GraphQLTaggedNode } from "react- relay";
import {
useRequête préchargée,
usePaginationFragment,
Requête préchargée,  
} de "react-relay";
import PodcastItem from "../../components/PodcastItem /PodcastItem";
import { PodcastsQuery } from "./__generated__/PodcastsQuery.graphql";
import  { PodcastsPaginationQuery } from "./__generated__/PodcastsPaginationQuery.graphql";
import { PodcastsResults_194590$key 

from "./__generated__/PodcastsResults_podcasts.graphql";

Nous souhaitons interroger une liste de podcasts et les afficher sur notre application. Nous ne voulons pas interroger tous les podcasts, sinon l'API prendrait trop de temps et la base de données recevrait beaucoup de requêtes, ce qui pourrait poser problème. Nous voulons obtenir une liste de podcasts et les parcourir—très simple.

Le crochet usePaginationFragment est parfait pour cela. Nous rendons un fragment GraphQL qui a une @connexion et pagination dessus.

Rappelez-vous que nous avons passé un fragment GraphQL dans notre requête ? C'est parce que nous allons utiliser le hook usePaginationFragment pour la pagination. Nous devons maintenant créer notre fragment GraphQL à l'aide du modèle graphql, qui ressemblera à ceci :

const fragment = graphql`
fragment PodcastsResults_podcasts sur requête
@argumentDefinitions(
après : { tapez : "String" }
first : { tapez : "Int", defaultValue : 20 }
avant : { tapez : "String" }
last : { tapez : "Int" }
)
@refetchable(queryName : "PodcastsPaginationQuery") {
balados(
après : $après
premier : $premier
avant : $avant
dernier : $dernier
) @connection(clé : "Podcast_podcasts") {
bords {
nœud {
identifiant
_identifiant
Nom
la description
image
}
}
}
}
`;

Ensuite, nous allons utiliser usePreloadedQuery. Nous passerons nos podcastsQuery et queryReference, que nous avons tous deux transmis en tant qu'accessoires à notre composant. Nous avons maintenant nos podcasts, mais nous devons créer un moyen de les parcourir, nous devons donc utiliser usePaginationFragment.

const query = usePreloadedQuery<_PodcastsQuery_ >(_podcastsQuery_, _queryReference_);

Le hook usePaginationFragment reçoit à la fois notre fragment et notre requête comme valeurs de réponse et renvoie un objet avec nos données et quelques rappels.

const { data, loadNext, isLoadingNext } = usePaginationFragment<
_PodcastsPaginationQuery_,
_PodcastsResults_podcasts$key_
>(fragment, requête);

Nous allons créer une fonction appelée loadMore pour charger plus de podcasts, qui ressemblera à ceci :

 const loadMore = useCallback(([19659015]) => {
  if (isLoadingNext) return;
  loadNext(20[19659015]);
}, [isLoadingNext, loadNext]);

Après tout cela, notre composant PodcastsResults ressemblera à ceci :

import React, { useCallback } from "react ";
import { VStack, StackDivider, Bouton } de "@chakra-ui/ react"
import graphql from "babel-plugin-relay/macro";
import { GraphQLTaggedNode } from  "react-relay";
import {
  useRequête préchargée,
  usePaginationFragment,
  Requête préchargée,
} de "react-relay";
import PodcastItem from "../../components/PodcastItem /PodcastItem";
import { PodcastsQuery } from "./__generated__/PodcastsQuery.graphql";
import  { PodcastsPaginationQuery } from "./__generated__/PodcastsPaginationQuery.graphql";
import { PodcastsResults_194590$key 

from "./__generated__/PodcastsResults_podcasts.graphql"; const fragment = graphql` fragment PodcastsResults_podcasts sur requête @argumentDefinitions( après : { tapez : "String" } first : { tapez : "Int", defaultValue : 20 } avant : { tapez : "String" } last : { tapez : "Int" } ) @refetchable(queryName : "PodcastsPaginationQuery") { balados( après : $après premier : $premier avant : $avant dernier : $dernier ) @connection(clé : "Podcast_podcasts") { bords { nœud { identifiant _identifiant Nom la description image } } } } `; interface Accessoires { podcastsQuery: GraphQLTaggedNode; queryReference: PreloadedQuery<PodcastsQuery>; } const PodcastsResults = ([1965 podcastsQuery, queryReference }: Props) => { const requête = usePreloadedQuery<PodcastsQuery>(podcastsQuery, queryReference); const {[1945, loadNext, isLoadingNext } = usePaginationFragment< PodcastsPaginationQuery, PodcastsResults_podcasts$key >(fragment, requête); const loadMore = useCallback([19659015]() => { if (isLoadingNext) return; loadNext( 20); }, [isLoadingNext, loadNext]); return[19659024]( <VStack diviseur={<StackDivider borderColor="gray.200" />} espacement={4} aligner="étirer" w="800px" h="100%" marge="0 auto" pt={3} pb={3} > {data.podcasts.edges.[19659052]map(({ node }: any) => <PodcastItem node={nœud} />)} <Bouton type= "button" width="120px" onClick={loadMore} justifySelf="center " alignSelf="center" isLoading={isLoadingNext}>Charger plus<[19659135]/Bouton> </VStack> ) }; export default Podcasts Résultats ;

Tout ce que nous avons à faire maintenant est d'exécuter le script Relay pour convertir nos littéraux GraphQL en fichiers générés. Nous avons maintenant un exemple d'application très performant et évolutif utilisant Relay.

Conclusion

Relay a été créé dans un souci d'évolutivité et de performances. Il s'agit d'un cadre solide et avisé qui vous aide à écrire des applications sécurisées. Relay est le meilleur client GraphQL si vous voulez avoir une application très robuste et évolutive qui restera performante.




Source link