Comprensión de GraphQl del lado del cliente con Apollo-Client en aplicaciones React
¿Alguna vez intentó interactuar con un servidor GraphQL en una aplicación del lado del cliente y tuvo ganas de darse por vencido incluso antes de llegar a ninguna parte? ¿Alguna vez rechazó una invitación para unirse a una base de código que requiere trabajar con la API GraphQL porque no tenía idea? ¿Alguna vez te has sentido como el único ingeniero front-end que no ha aprendido a consumir API GraphQL? Si respondió afirmativamente a alguna de estas preguntas, entonces este tutorial es para usted. Analizaremos más de cerca algunos conceptos básicos de GraphQL y Apollo Client, así como también cómo trabajar con ambos. Al final, habremos creado una aplicación de tienda de mascotas que utiliza Apollo Client. Luego, podrá continuar con la construcción de su próximo proyecto.
Según State of JavaScript 2019 , al 38,7% de los desarrolladores les gustaría utilizar GraphQL, mientras que al 50,8% de los desarrolladores les gustaría aprender GraphQL.
Al ser un lenguaje de consulta, GraphQL simplifica el flujo de trabajo de creación de una aplicación cliente. Elimina la complejidad de administrar puntos finales API en aplicaciones del lado del cliente porque expone un único punto final HTTP para recuperar los datos requeridos. Por lo tanto, elimina la recuperación excesiva y insuficiente de datos , como en el caso de REST.
Pero GraphQL es sólo un lenguaje de consulta. Para poder utilizarlo fácilmente, necesitamos una plataforma que haga el trabajo pesado por nosotros. Una de esas plataformas es Apollo.
La plataforma Apollo es una implementación de GraphQL que transfiere datos entre la nube (el servidor) a la interfaz de usuario de su aplicación. Cuando utiliza Apollo Client, toda la lógica para recuperar datos, rastrear, cargar y actualizar la interfaz de usuario está encapsulada en el useQuerygancho (como en el caso de React). Por tanto, la obtención de datos es declarativa. También tiene almacenamiento en caché sin configuración. Con solo configurar Apollo Client en su aplicación, obtiene un caché inteligente listo para usar, sin necesidad de configuración adicional.
Apollo Client también es interoperable con otros marcos, como Angular, Vue.js y React.
Nota : Este tutorial beneficiará a aquellos que han trabajado con RESTful u otras formas de API en el pasado en el lado del cliente y quieren ver si vale la pena probar GraphQL. Esto significa que deberías haber trabajado con una API antes; sólo entonces podrá comprender lo beneficioso que puede ser GraphQL para usted. Si bien cubriremos algunos conceptos básicos de GraphQL y Apollo Client, un buen conocimiento de JavaScript y React Hooks será útil.
Conceptos básicos de GraphQL
Este artículo no es una introducción completa a GraphQL, pero definiremos algunas convenciones antes de continuar.
¿Qué es GraphQL?
GraphQL es una especificación que describe un lenguaje de consulta declarativo que sus clientes pueden usar para solicitar a una API los datos exactos que desean. Esto se logra mediante la creación de un esquema de tipos sólido para su API, con la máxima flexibilidad. También garantiza que la API resuelva los datos y que las consultas de los clientes se validen con un esquema. Esta definición significa que GraphQL contiene algunas especificaciones que lo convierten en un lenguaje de consulta declarativo, con una API de tipo estático (construida alrededor de Typecript) y que permite al cliente aprovechar esos sistemas de tipos para solicitar a la API los datos exactos que desea. .
Entonces, si creamos algunos tipos con algunos campos, entonces, desde el lado del cliente, podríamos decir: "Danos estos datos con estos campos exactos". Entonces la API responderá con esa forma exacta, como si estuviéramos usando un sistema de tipos en un lenguaje fuertemente tipado. Puedes obtener más información en mi artículo de Typecript .
Veamos algunas convenciones de GraphQl que nos ayudarán a medida que continuamos.
Los basicos
- Operaciones
En GraphQL, cada acción realizada se denomina operación. Hay algunas operaciones, a saber:- Consulta
Esta operación se ocupa de obtener datos del servidor. También podría llamarlo recuperación de solo lectura. - Mutación
Esta operación implica crear, actualizar y eliminar datos de un servidor. Popularmente se denomina operación CUD (crear, actualizar y eliminar). - Suscripciones
Esta operación en GraphQL implica enviar datos desde un servidor a sus clientes cuando ocurren eventos específicos. Generalmente se implementan con WebSockets .
- Consulta
En este artículo, nos ocuparemos únicamente de las operaciones de consulta y mutación.
- Nombres de operaciones
Hay nombres únicos para las operaciones de mutación y consulta del lado del cliente. - Variables y argumentos
Las operaciones pueden definir argumentos, de forma muy parecida a una función en la mayoría de los lenguajes de programación. Luego, esas variables se pueden pasar a llamadas de consulta o mutación dentro de la operación como argumentos. Se espera que las variables se proporcionen en tiempo de ejecución durante la ejecución de una operación desde su cliente. - Alias
Esta es una convención en GraphQL del lado del cliente que implica cambiar el nombre de los nombres de campos detallados o vagos con nombres de campos simples y legibles para la interfaz de usuario. El alias es necesario en casos de uso en los que no desea tener nombres de campos conflictivos.
¿Qué es GraphQL del lado del cliente?
Cuando un ingeniero de front-end crea componentes de interfaz de usuario utilizando cualquier marco, como Vue.js o (en nuestro caso) React, esos componentes se modelan y diseñan a partir de un patrón determinado en el cliente para adaptarse a los datos que se obtendrán del servidor.
Uno de los problemas más comunes con las API RESTful es la recuperación excesiva o insuficiente. Esto sucede porque la única forma que tiene un cliente de descargar datos es accediendo a puntos finales que devuelvan estructuras de datos fijas . La recuperación excesiva en este contexto significa que un cliente descarga más información de la que requiere la aplicación.
En GraphQL, por otro lado, simplemente enviaría una única consulta al servidor GraphQL que incluye los datos requeridos. Luego, el servidor responderá con un objeto JSON con los datos exactos que usted solicitó; por lo tanto, no habrá sobrecaptación. Sebastian Eschweiler explica las diferencias entre las API RESTful y GraphQL .
GraphQL del lado del cliente es una infraestructura del lado del cliente que interactúa con datos de un servidor GraphQL para realizar las siguientes funciones:
- Gestiona datos enviando consultas y mutando datos sin que usted tenga que crear solicitudes HTTP usted mismo. Puede dedicar menos tiempo a recopilar datos y más tiempo a crear la aplicación real.
- Gestiona la complejidad de un caché por usted. Por lo tanto, puede almacenar y recuperar los datos obtenidos del servidor, sin interferencia de terceros, y evitar fácilmente recuperar recursos duplicados. Por lo tanto, identifica cuándo dos recursos son iguales, lo cual es excelente para una aplicación compleja.
- Mantiene su interfaz de usuario consistente con la interfaz de usuario optimista, una convención que simula los resultados de una mutación (es decir, los datos creados) y actualiza la interfaz de usuario incluso antes de recibir una respuesta del servidor. Una vez que se recibe la respuesta del servidor, el resultado optimista se descarta y se reemplaza con el resultado real.
Para obtener más información sobre GraphQL del lado del cliente, dedique una hora al cocreador de GraphQL y otras personas interesantes en GraphQL Radio .
¿Qué es el cliente Apollo?
Apollo Client es un cliente GraphQL interoperable , ultraflexible e impulsado por la comunidad para JavaScript y plataformas nativas. Sus impresionantes características incluyen una sólida herramienta de administración de estado ( Apollo Link ), un sistema de almacenamiento en caché sin configuración , un enfoque declarativo para recuperar datos, paginación fácil de implementar y la interfaz de usuario optimista para su aplicación del lado del cliente.
Apollo Client almacena no sólo el estado de los datos obtenidos del servidor, sino también el estado que ha creado localmente en su cliente; por lo tanto, gestiona el estado tanto de los datos API como de los datos locales.
También es importante tener en cuenta que puede utilizar Apollo Client junto con otras herramientas de gestión de estado, como Redux, sin conflictos. Además, es posible migrar su administración de estado desde, por ejemplo, Redux al Cliente Apollo (lo cual está más allá del alcance de este artículo). En última instancia, el objetivo principal de Apollo Client es permitir a los ingenieros consultar datos en una API sin problemas.
Características del cliente Apollo
Apollo Client ha conquistado a muchos ingenieros y empresas debido a sus características extremadamente útiles que facilitan la creación de aplicaciones modernas y robustas. Las siguientes características vienen integradas:
- Almacenamiento en caché
Apollo Client admite el almacenamiento en caché sobre la marcha. - Optimistic UI
Apollo Client tiene un excelente soporte para Optimistic UI. Implica mostrar temporalmente el estado final de una operación (mutación) mientras la operación está en curso. Una vez completada la operación, los datos reales reemplazan a los datos optimistas. - Paginación
Apollo Client tiene una funcionalidad incorporada que hace que sea bastante fácil implementar la paginación en su aplicación. Se encarga de la mayoría de los dolores de cabeza técnicos que supone obtener una lista de datos, ya sea en parches o de una vez, utilizando lafetchMorefunción que viene con eluseQuerygancho.
En este artículo, veremos una selección de estas características.
Basta ya de teoría. Abróchate el cinturón y tómate una taza de café para acompañar tus panqueques, mientras nos ensuciamos las manos.
Construyendo nuestra aplicación web
Este proyecto está inspirado en Scott Moss.
Crearemos una aplicación web sencilla para una tienda de mascotas, cuyas características incluyen:
- buscar nuestras mascotas desde el lado del servidor;
- crear una mascota (lo que implica crear el nombre, el tipo de mascota y la imagen);
- usando la interfaz de usuario optimista;
- usando la paginación para segmentar nuestros datos.
Para comenzar, clone el repositorio, asegurándose de que la starterrama sea la que ha clonado.
Empezando
- Instale la extensión Apollo Client Developer Tools para Chrome.
- Usando la interfaz de línea de comandos (CLI), navegue hasta el directorio del repositorio clonado y ejecute el comando para obtener todas las dependencias:
npm install. - Ejecute el comando
npm run apppara iniciar la aplicación. - Mientras aún estás en la carpeta raíz, ejecuta el comando
npm run server. Esto iniciará nuestro servidor back-end, que usaremos a medida que avancemos.
La aplicación debería abrirse en un puerto configurado. El mio es https://localhost:1234/; Lo tuyo probablemente sea otra cosa.
Si todo funcionó bien, tu aplicación debería verse así:
Notarás que no tenemos mascotas para mostrar. Esto se debe a que aún no hemos creado dicha funcionalidad.
Si ha instalado correctamente las herramientas de desarrollo del cliente Apollo , abra las herramientas de desarrollo y haga clic en el icono de la bandeja. Verás "Apolo" y algo como esto:
Al igual que las herramientas de desarrollo Redux y React, usaremos las herramientas de desarrollo del cliente Apollo para escribir y probar nuestras consultas y mutaciones. La extensión viene con GraphQL Playground .
Buscando mascotas
Agreguemos la funcionalidad que busca mascotas. Pasa a client/src/client.js. Escribiremos Apollo Client, lo vincularemos a una API, lo exportaremos como cliente predeterminado y escribiremos una nueva consulta.
Copie el siguiente código y péguelo en client.js:
import { ApolloClient } from 'apollo-client'import { InMemoryCache } from 'apollo-cache-inmemory'import { HttpLink } from 'apollo-link-http'const link = new HttpLink({ uri: 'https://localhost:4000/' })const cache = new InMemoryCache()const client = new ApolloClient({ link, cache})export default client
Aquí hay una explicación de lo que está sucediendo arriba:
ApolloClient
Esta será la función que envuelve nuestra aplicación y, por lo tanto, interactúa con HTTP, almacena en caché los datos y actualiza la interfaz de usuario.InMemoryCache
Este es el almacén de datos normalizado en Apollo Client que ayuda a manipular el caché en nuestra aplicación.HttpLink
Esta es una interfaz de red estándar para modificar el flujo de control de las solicitudes GraphQL y obtener resultados de GraphQL. Actúa como middleware, obteniendo resultados del servidor GraphQL cada vez que se activa el enlace. Además, es un buen sustituto de otras opciones, comoAxiosywindow.fetch.- Declaramos una variable de enlace que se asigna a una instancia de
HttpLink. Se necesita unauripropiedad y un valor para nuestro servidor, que eshttps://localhost:4000/. - La siguiente es una variable de caché que contiene la nueva instancia de
InMemoryCache. - La variable de cliente también toma una instancia de
ApolloClienty envuelvelinkycache. - Por último, exportamos el
clientpara poder usarlo en toda la aplicación.
Antes de ver esto en acción, debemos asegurarnos de que toda nuestra aplicación esté expuesta a Apollo y que nuestra aplicación pueda recibir datos obtenidos del servidor y que pueda mutar esos datos.
Para lograr esto, vayamos a client/src/index.js:
import React from 'react'import ReactDOM from 'react-dom'import { BrowserRouter } from 'react-router-dom'import { ApolloProvider } from '@apollo/react-hooks'import App from './components/App'import client from './client'import './index.css'const Root = () = ( BrowserRouterApolloProvider client={client} App / /ApolloProvider/BrowserRouter);ReactDOM.render(Root /, document.getElementById('app'))if (module.hot) { module.hot.accept()}
Como notará en el código resaltado, hemos incluido el Appcomponente ApolloProvidery pasado el cliente como accesorio al archivo client. ApolloProvideres similar al de React Context.Provider. Envuelve su aplicación React y coloca al cliente en contexto, lo que le permite acceder a él desde cualquier lugar de su árbol de componentes.
Para recuperar nuestras mascotas del servidor, necesitamos escribir consultas que soliciten los campos exactos que queremos. Dirígete a client/src/pages/Pets.jsy copia y pega el siguiente código en él:
import React, {useState} from 'react'import gql from 'graphql-tag'import { useQuery, useMutation } from '@apollo/react-hooks'import PetsList from '../components/PetsList'import NewPetModal from '../components/NewPetModal'import Loader from '../components/Loader'const GET_PETS = gql` query getPets { pets { id name type img } }`;export default function Pets () { const [modal, setModal] = useState(false)const { loading, error, data } = useQuery(GET_PETS); if (loading) return Loader /; if (error) return pAn error occured!/p;const onSubmit = input = { setModal(false) } if (modal) { return NewPetModal onSubmit={onSubmit} onCancel={() = setModal(false)} / } return ( div className="page pets-page" section div className="row betwee-xs middle-xs" div className="col-xs-10" h1Pets/h1 /div div className="col-xs-2" button onClick={() = setModal(true)}new pet/button /div /div /section sectionPetsList pets={data.pets}//section /div )}
Con unos pocos bits de código, podemos recuperar las mascotas del servidor.
¿Qué es gql?
Es importante tener en cuenta que las operaciones en GraphQL son generalmente objetos JSON escritos graphql-tagcon comillas invertidas.
gqlLas etiquetas son etiquetas literales de plantilla de JavaScript que analizan cadenas de consulta GraphQL en GraphQL AST (árbol de sintaxis abstracta) .
- Operaciones de consulta
Para poder recuperar nuestras mascotas del servidor, necesitamos realizar una operación de consulta.- Debido a que estamos realizando una
queryoperación, necesitábamos especificar latypeoperación antes de nombrarla. - El nombre de nuestra consulta es
GET_PETS. Es una convención de nomenclatura de GraphQL usar camelCase para los nombres de campos. - El nombre de nuestros campos es
pets. Por lo tanto, especificamos los campos exactos que necesitamos del servidor(id, name, type, img). useQueryes un gancho de React que es la base para ejecutar consultas en una aplicación Apollo. Para realizar una operación de consulta en nuestro componente React, llamamos aluseQuerygancho, que inicialmente se importó desde@apollo/react-hooks. A continuación, le pasamos una cadena de consulta GraphQL, que esGET_PETSen nuestro caso.
- Debido a que estamos realizando una
- Cuando nuestro componente se procesa,
useQuerydevuelve una respuesta de objeto de Apollo Client que contiene propiedades de carga, error y datos. Por lo tanto, están desestructurados para que podamos usarlos para representar la interfaz de usuario. useQueryes increíble. No tenemos que incluirasync-await. Ya se ha solucionado en segundo plano. Muy bueno, ¿no?loading
Esta propiedad nos ayuda a manejar el estado de carga de la aplicación. En nuestro caso, devolvemos unLoadercomponente mientras se carga nuestra aplicación. De forma predeterminada, la carga esfalse.error
Por si acaso, usamos esta propiedad para manejar cualquier error que pueda ocurrir.data
Este contiene nuestros datos reales del servidor.- Por último, en nuestro
PetsListcomponente, pasamos lospetsaccesorios,data.petscomo valor de objeto.
En este punto, hemos consultado exitosamente nuestro servidor.
Para iniciar nuestra aplicación, ejecutemos el siguiente comando:
- Inicie la aplicación cliente. Ejecute el comando
npm run appen su CLI. - Inicie el servidor. Ejecute el comando
npm run serveren otra CLI.
Si todo ha ido bien deberías ver esto:
Datos mutantes
Mutar datos o crear datos en Apollo Client es casi lo mismo que consultar datos, con cambios muy leves.
Aún en client/src/pages/Pets.js, copiemos y peguemos el código resaltado:
....const GET_PETS = gql` query getPets { pets { id name type img } }`;const NEW_PETS = gql` mutation CreateAPet($newPet: NewPetInput!) { addPet(input: $newPet) { id name type img } }`;const Pets = () = { const [modal, setModal] = useState(false) const { loading, error, data } = useQuery(GET_PETS);const [createPet, newPet] = useMutation(NEW_PETS);const onSubmit = input = { setModal(false)createPet({ variables: { newPet: input } }); } if (loading || newPet.loading) return Loader /; if (error || newPet.error) return pAn error occured/p;if (modal) { return NewPetModal onSubmit={onSubmit} onCancel={() = setModal(false)} / } return ( div className="page pets-page" section div className="row betwee-xs middle-xs" div className="col-xs-10" h1Pets/h1 /div div className="col-xs-2" button onClick={() = setModal(true)}new pet/button /div /div /section section PetsList pets={data.pets}/ /section /div )}export default Pets
Para crear una mutación, seguiríamos los siguientes pasos.
1.mutation
Para crear, actualizar o eliminar, debemos realizar la mutationoperación. La mutationoperación tiene un CreateAPetnombre, con un argumento. Este argumento tiene una $newPetvariable, con un tipo de NewPetInput. Los !medios por los que se requiere la operación; por lo tanto, GraphQL no ejecutará la operación a menos que pasemos una newPetvariable cuyo tipo sea NewPetInput.
2.addPet
La addPetfunción, que está dentro de la mutationoperación, toma un argumento de inputy se establece en nuestra $newPetvariable. Los conjuntos de campos especificados en nuestra addPetfunción deben ser iguales a los conjuntos de campos en nuestra consulta. Los conjuntos de campos en nuestra operación son:
idnametypeimg
3.useMutation
El useMutation gancho React es la API principal para ejecutar mutaciones en una aplicación Apollo. Cuando necesitamos mutar datos, llamamos useMutationa un componente de React y le pasamos una cadena GraphQL (en nuestro caso, NEW_PETS).
Cuando nuestro componente procesa useMutation, devuelve una tupla (es decir, un conjunto ordenado de datos que constituye un registro) en una matriz que incluye:
- una
mutatefunción que podemos llamar en cualquier momento para ejecutar la mutación; - un objeto con campos que representan el estado actual de ejecución de la mutación.
Al useMutationgancho se le pasa una cadena de mutación GraphQL (que es NEW_PETSen nuestro caso). Desestructuramos la tupla, que es la función ( createPet) que mutará los datos y el campo objeto ( newPets).
4.createPet
En nuestra onSubmitfunción, poco después del setModalestado, definimos nuestro createPet. Esta función toma a variablecon una propiedad de objeto de un valor establecido en { newPet: input }. Representa inputlos diversos campos de entrada en nuestro formulario (como nombre, tipo, etc.).
Una vez hecho esto, el resultado debería verse así:
Si observas el GIF de cerca, notarás que nuestra mascota creada no aparece instantáneamente, solo cuando se actualiza la página. Sin embargo, se ha actualizado en el servidor.
La gran pregunta es ¿por qué nuestra mascota no se actualiza al instante? Averigüemos en la siguiente sección.
Almacenamiento en caché en el cliente Apollo
La razón por la que nuestra aplicación no se actualiza automáticamente es que nuestros datos recién creados no coinciden con los datos de caché en Apollo Client. Por lo tanto, existe un conflicto sobre qué es exactamente lo que se debe actualizar desde el caché.
En pocas palabras, si realizamos una mutación que actualiza o elimina múltiples entradas (un nodo), entonces somos responsables de actualizar cualquier consulta que haga referencia a ese nodo, de modo que modifique nuestros datos almacenados en caché para que coincidan con las modificaciones que una mutación realiza en nuestro back-end . datos finales .
Mantener el caché sincronizado
Hay algunas formas de mantener nuestro caché sincronizado cada vez que realizamos una operación de mutación.
La primera es recuperar consultas coincidentes después de una mutación, utilizando la refetchQueriespropiedad del objeto (la forma más sencilla).
Nota: Si usáramos este método, tomaría una propiedad de objeto en nuestra createPetfunción llamada refetchQueriesy contendría una matriz de objetos con un valor de la consulta refetchQueries: [{ query: GET_PETS }]:.
Debido a que nuestro enfoque en esta sección no es solo actualizar nuestras mascotas creadas en la interfaz de usuario, sino también manipular el caché, no usaremos este método.
El segundo enfoque es utilizar la updatefunción. En Apollo Client, hay una updatefunción auxiliar que ayuda a modificar los datos de la caché, de modo que se sincronice con las modificaciones que realiza una mutación en nuestros datos de back-end. Usando esta función, podemos leer y escribir en el caché.
Actualizando el caché
Copie el siguiente código resaltado y péguelo client/src/pages/Pets.js:
......const Pets = () = { const [modal, setModal] = useState(false) const { loading, error, data } = useQuery(GET_PETS);const [createPet, newPet] = useMutation(NEW_PETS, { update(cache, { data: { addPet } }) { const data = cache.readQuery({ query: GET_PETS }); cache.writeQuery({ query: GET_PETS, data: { pets: [addPet, ...data.pets] }, }); }, } );.....
La updatefunción recibe dos argumentos:
- El primer argumento es el caché del cliente Apollo.
- La segunda es la respuesta de mutación exacta del servidor. Deestructuramos la
datapropiedad y la configuramos en nuestra mutación (addPet).
A continuación, para actualizar la función, debemos verificar qué consulta debe actualizarse (en nuestro caso, la GET_PETSconsulta) y leer el caché.
En segundo lugar, debemos escribir en el queryarchivo leído para que sepa que estamos a punto de actualizarlo. Lo hacemos pasando un objeto que contiene una querypropiedad de objeto, con el valor establecido en nuestra queryoperación ( GET_PETS), y una datapropiedad cuyo valor es un petobjeto y que tiene una matriz de la addPetmutación y una copia de los datos de la mascota.
Si siguió estos pasos cuidadosamente, debería ver que sus mascotas se actualizan automáticamente a medida que las crea. Echemos un vistazo a los cambios:
UI optimista
Mucha gente es gran fanática de los cargadores y las hiladoras. No hay nada de malo en usar un cargador; Hay casos de uso perfectos en los que un cargador es la mejor opción. He escrito sobre cargadores versus hilanderos y sus mejores casos de uso.
De hecho, los cargadores y los hilanderos juegan un papel importante en el diseño de UI y UX, pero la llegada de Optimistic UI se ha robado el centro de atención.
¿Qué es la interfaz de usuario optimista?
La UI optimista es una convención que simula los resultados de una mutación (datos creados) y actualiza la UI antes de recibir una respuesta del servidor. Una vez que se recibe la respuesta del servidor, el resultado optimista se descarta y se reemplaza con el resultado real.
Al final, una interfaz de usuario optimista no es más que una forma de gestionar el rendimiento percibido y evitar estados de carga.
Apollo Client tiene una forma muy interesante de integrar la interfaz de usuario optimista. Nos brinda un gancho simple que nos permite escribir en el caché local después de la mutación. ¡Vamos a ver cómo funciona!
Paso 1
Dirígete a client/src/client.jsy agrega solo el código resaltado.
import { ApolloClient } from 'apollo-

Deja un comentario