Предположения:
Знакомство с React 16+ и хуками React
Знание асинхронного программирования в JavaScript и синтаксисе ES6+
Какая лучшая библиотека для получения, кэширования, синхронизации и обновления состояния сервера в приложении React? И почему это React Query? :)
При работе с состоянием клиента библиотека React является отличным инструментом для создания сложных пользовательских интерфейсов. Начиная с версии 16.8, React Hooks позволяет нам использовать больше функций React даже без написания класса.
Однако работа с состоянием сервера отличается. Вот почему:
- Он сохраняется удаленно
- Для получения данных требуются асинхронные API.
- Ранее полученные данные могут быть «устаревшими», вам необходимо знать, когда это происходит, и обновлять их как можно быстрее.
Скажем, у нас есть собственный хук или библиотека управления состоянием для решения этих задач, нам также может понадобиться:
- Дедупликация нескольких запросов одних и тех же данных в один запрос
- Реализовать кэширование и другие оптимизации производительности
Следует признать, что решение всех этих проблем часто приводит к многословному коду, сложной логике и менее отзывчивым приложениям React с сетевой задержкой.
И это мотивация React Query! У него есть настройки по умолчанию, которые дают отличные результаты без настройки. React Query позволяет нам хранить код извлечения данных в компонентах, которым нужны данные, но за кулисами он управляет кешем данных, передавая уже извлеченные данные компонентам, когда они их запрашивают.
Давайте теперь разберем пример с печально известным Pokemon API с помощью React Query. (Sисходные файлы в CodeSandbox).
Наши цели:
1. Получите данные для 12 pokemons
, отправив запрос GET
на https://pokeapi.co.api/v2/pokemon?limit=12
.
Response: { ... "results": [ { "name": "bulbasaur", "url": "https://pokeapi.co/api/v2/pokemon/1/" }, { "name": "ivysaur", "url": "https://pokeapi.co/api/v2/pokemon/2/" }, ... ] }
2. Получить данные для каждого pokemon
, отправив запрос GET
к его свойству url
из предыдущего ответа.
3. Представьте name
, image
и type
всех покемонов на карточке, используя React и немного CSS.
Для начала напишем две функции, которые позволят нам отправлять асинхронные HTTP-запросы на эти конечные точки. Также мы будем использовать Axios, просто из предпочтения. Fetch API одинаково прекрасно работает и используется в официальных примерах. Каждая функция имеет описательное имя, как показано ниже:
const fetchPokemons = async () => { const { data } = await axios.get("https://pokeapi.co.api/v2/pokemon?limit=12"); const pokemons = await Promise.all(data.results.map(getPokemonData)); return pokemons; }; const getPokemonData = async ({ url }) => { const { data: { id, name, sprites, types } } = await axios.get(url); return { id, name, img: sprites.other["official-artwork"]?.front_default, pokemonType: types[0].type.name }; };
Объяснение: мы используем деструктуризацию объекта, чтобы получить вложенные данные из ответа JSON. Сопоставление через results в fetchPokemons возвращает список промисов, а метод Promise.all принимает этот список и обрабатывает его параллельно.
Затем мы завернем наше дерево компонентов в QueryClientProvider.
React Query, который будет использовать это, чтобы сделать объект QueryClient
доступным, чтобы мы могли вызывать хуки из библиотеки в компонентах-потомках. Мы делаем это в App.js
вот так:
import { QueryClient, QueryClientProvider } from "react-query"; import Pokemons from "./components/Pokemons"; const queryClient = new QueryClient(); export default function App() { return ( <QueryClientProvider client={queryClient}> <Pokemons /> </QueryClientProvider> ); }
Хук useQuery
в React Query похож на то, как мы реализуем пользовательские хуки React. Разница в том, что мы передаем useQuery
в key
и асинхронную функцию для получения объекта со свойствами data
,status
, и error
.
const { data, status, error } = useQuery(key, () => asyncFunction(url));
key
используется для идентификации данных в кэше; он может сразу же вернуть данные, соответствующие существующим ключам, а затем в фоновом режиме получить последние данные с сервера. key
может быть string
, array
или сериализованным object
. В Pokemons.js
мы используем строку pokemons
вот так:
import { useQuery } from "react-query"; import fetchPokemons from "../api"; export default function Pokemons() { const { data: pokemons, isLoading, error } = useQuery("pokemons", () => fetchPokemons()); if (error) return <p>{error.message}</p>; if (isLoading) return <p>Loading....</p>; return ( <div> {pokemons.map(({ id, img, name, pokemonType }) => ( <div key={id}> <img src={img} alt={name} /> <div> <h2>{name}</h2> <span>{pokemonType}</span> </div> </div> ))} </div> ); }
В компоненте мы получили isLoading — состояние, которое говорит нам, что запрос не имеет данных и в настоящее время извлекается. Подробнее о результатах запроса и состояниях в документации.
Ага! Это все, что нам нужно сделать. React Query делает кеш доступным, если мы хотим напрямую получить доступ к извлеченным данным или каким-либо образом манипулировать ими. В другом месте другой компонент может использовать связанный ключ запроса и метод getQueryData
для доступа к уже извлеченным данным. Хук useQuery
также принимает объект config
в качестве третьего аргумента, что позволяет нам управлять всеми видами функций, связанных с запросами, такими как истечение срока действия кэша, политики повторных попыток при возникновении ошибок выборки, функции обратного вызова, необходимость работы с границами ошибки или заданными исходными данными. С другой стороны, если мы также хотим обновить состояние на сервере, React Query предоставляет для этой цели хук useMutation
.
Вот скриншот нашего примера проекта:
Просмотреть исходные файлы в CodeSandbox
React Query помогает синхронизировать пользовательский интерфейс приложения React с состоянием, хранящимся на сервере. Посетите документацию React Query, чтобы найти примеры и ссылки на дополнительные учебные ресурсы об этой мощной библиотеке.