Введение в SWR: React Hooks для удаленной выборки данных

В этой статье мы рассмотрим новый способ получения данных в React Apps под названием SWR. Это набор хуков для удаленного получения данных, который упрощает такие вещи, как кэширование, пагинация и так далее. Мы также создадим приложение Pokedex с нуля и используем возможности SWR для получения данных и их постраничной публикации.


SWR - это легкая библиотека, созданная компанией Vercel (ранее ZEIT), которая позволяет получать, кэшировать или повторно получать данные в реальном времени с помощью React Hooks. Она построена с использованием React Suspense, который позволяет вашим компонентам "ждать" чего-то, прежде чем они смогут отобразиться, включая данные. SWR поставляется с такими замечательными функциями, как зависимая выборка, фокус на переоценке, восстановление позиции прокрутки и так далее. Это также очень мощный инструмент, поскольку он не зависит от бэкенда и имеет хорошую поддержку TypeScript. Это пакет, у которого большое будущее.

Почему вас это должно волновать? Это должно вас заинтересовать, если вы искали библиотеку, которая позволяет не только получать данные из API, но и делать такие вещи, как кэширование и зависимая выборка. То, что будет рассмотрено в этом руководстве, пригодится вам при создании приложений React с большим количеством подвижных частей. Предполагается, что вы уже использовали Axios и Fetch API, хотя мы и сравним, чем они отличаются от SWR, мы не будем вдаваться в подробности их реализации.

В этом руководстве я познакомлю вас с React Hooks for Remote Data Fetching на примере создания приложения Pokedex, которое запрашивает данные из Pokemon API. Мы также погрузимся в другие возможности, которые предоставляет SWR, выделим его отличия от таких популярных решений, как Fetch API и библиотека Axios, и расскажем вам о причинах использования этой библиотеки и о том, почему вам стоит обратить внимание на SWR.

Итак, давайте начнем с ответа на фундаментальный вопрос: Что такое SWR?

Что такое SWR?

SWR - это аббревиатура от stale-while-revalidate. Это библиотека React Hooks для удаленной выборки данных. SWR работает в три основных этапа: сначала он возвращает данные из кэша (устаревшая часть), затем отправляет запрос на выборку (часть, связанная с перепроверкой) и, наконец, приходит с актуальными данными. Но не беспокойтесь, SWR обрабатывает все эти шаги за нас. Единственное, что нам нужно сделать, это передать хуку useSWR необходимые параметры для выполнения запроса.

SWR также обладает некоторыми приятными особенностями, такими как:

  • Независимость от бэкенда
  • Быстрая навигация по странице
  • Переоценка по фокусу
  • Интервальный опрос
  • Дедупликация запросов
  • Локальная мутация
  • Пагинация
  • TypeScript готов
  • Поддержка SSR
  • Режим приостановки
  • Поддержка React Native
  • Легкий вес.

Звучит волшебно? Ну, SWR упрощает вещи и повышает пользовательский опыт вашего React приложения. И как только мы начнем внедрять его в наш проект, вы поймете, почему этот хук очень удобен.

Важно знать, что название пакета - swr или SWR, а хук, используемый для получения возможностей SWR, называется useSWR.

Теоретически, SWR - это то, что вам нужно, чтобы улучшить выборку данных. Однако у нас уже есть два отличных способа выполнения HTTP-запросов в нашем приложении: Fetch API и библиотека Axios.

Итак, зачем использовать новую библиотеку для получения данных? Попробуем ответить на этот закономерный вопрос в следующем разделе.

Сравнение с Fetch и Axios

В наших приложениях React уже есть множество способов выполнения HTTP-запросов, и два из самых популярных - это API Fetch и библиотека Axios. Они оба великолепны и позволяют нам легко получать или отправлять данные. Однако, как только операция будет выполнена, они не помогут нам кэшировать или пагинации данных, вы должны сделать это самостоятельно.

Axios или Fetch просто обработают запрос и вернут ожидаемый ответ, не более того.

И по сравнению с SWR, это немного другое, потому что SWR под капотом использует Fetch API для запроса данных с сервера - это своего рода слой, построенный поверх него. Тем не менее, он обладает некоторыми хорошими функциями, такими как кэширование, пагинация, восстановление позиции прокрутки, зависимая выборка и т.д., и, если быть точным, определенным уровнем реактивности из коробки, которого нет у Axios или Fetch. Это большое преимущество, потому что наличие таких возможностей помогает сделать наши React-приложения быстрыми и удобными в использовании и заметно уменьшить размер кода.

И в заключение, просто помните, что SWR - это не то же самое, что Axios или Fetch, даже если они помогают работать с HTTP-запросами. SWR более продвинут, чем они, он обеспечивает некоторые улучшения для синхронизации нашего приложения с back-end и, следовательно, увеличивает производительность нашего приложения.

Теперь мы знаем, какие отличия имеет SWR по сравнению с библиотекой Axios или Fetch API, пришло время разобраться, зачем использовать такой инструмент.

Рекомендуем прочитать: Потребление REST API в React с помощью Fetch и Axios

Почему стоит использовать SWR для получения данных?

Как я уже говорил, SWR поставляется с некоторыми удобными функциями, которые помогают легко повысить удобство использования вашего приложения. С помощью SWR вы можете в кратчайшие сроки разместить данные по страницам, используя useSWRPages, вы также можете получить данные, которые зависят от другого запроса, или восстановить позицию прокрутки при возврате на заданную страницу, и многое другое.

Обычно при получении данных с сервера мы показываем пользователю сообщение о загрузке или спиннер. А с помощью SWR вы можете сделать это лучше, показывая пользователю кэшированные или устаревшие данные во время получения новых данных из API. И как только эта операция будет выполнена, он проведет ревалидацию данных, чтобы показать новую версию. И вам не нужно ничего делать, SWR будет кэшировать данные при первом получении и автоматически извлекать их при новом запросе.

Итак, мы уже видим, почему использование SWR вместо Axios или Fetch лучше, в зависимости от того, что именно вы хотите построить. Но для многих случаев я рекомендую использовать SWR, потому что у него есть отличные возможности, которые выходят за рамки простого получения и возврата данных.

Итак, мы можем приступить к созданию нашего приложения React и использовать библиотеку SWR для получения удаленных данных.

Итак, давайте начнем с создания нового проекта.

Настройка

Как я уже говорил во введении, мы создадим приложение, которое будет получать данные из API Pokemon. Вы можете использовать и другой API, если хотите, но я пока буду придерживаться его.

Чтобы создать новое приложение, нам нужно выполнить следующую команду в терминале:

npx create-react-app react-swr

Далее нам нужно установить библиотеку SWR, сначала перейдя в папку с приложением React.

cd react-swr

И выполните в терминале следующую команду для установки пакета SWR.

yarn add swr

Или если вы используете npm:

npm install swr

Теперь у нас все готово, давайте структурируем проект следующим образом, чтобы начать использовать SWR:

src
├── components
| └── Pokemon.js
├── App.js
├── App.test.js
├── index.js
├── serviceWorker.js
├── setupTests.js
├── package.json
├── README.md
├── yarn-error.log
└── yarn.lock

Как вы можете видеть, структура папок проста. Единственное, на что следует обратить внимание, это папка components, в которой находится файл Pokemon.js. Он будет использоваться позже в качестве презентационного компонента для показа одного покемона, когда мы получим данные от API.

Отлично! Теперь, когда все готово, мы можем начать получать данные из API с помощью useSWR.

Получение удаленных данных

Пакет SWR имеет несколько удобных функций, как мы видели выше. Однако существует два способа настройки этой библиотеки: локально или глобально.

Локальная настройка означает, что каждый раз, когда мы создаем новый файл, нам придется заново настраивать SWR, чтобы иметь возможность получать удаленные данные. А глобальная настройка позволяет нам повторно использовать часть нашей конфигурации в разных файлах, поскольку функция fetcher может быть объявлена один раз и использоваться везде.

И не беспокойтесь, мы рассмотрим оба варианта в этой статье, а пока давайте запачкаем руки и добавим немного осмысленного кода в файл App.js.

Отображение данных

import React from 'react'
import useSWR from 'swr'
import { Pokemon } from './components/Pokemon'

const url = 'https://pokeapi.co/api/v2/pokemon'

const fetcher = (...args) => fetch(...args).then((res) => res.json())

function App() {
  const { data: result, error } = useSWR(url, fetcher)

  if (error) return <h1>Something went wrong!</h1>
  if (!result) return <h1>Loading...</h1>

  return (
    <main className='App'>
      <h1>Pokedex</h1>
      <div>
        {result.results.map((pokemon) => (
          <Pokemon key={pokemon.name} pokemon={pokemon} />
        ))}
      </div>
    </main>
  )
}

export default App

Как вы видите, мы начинаем с импорта useSWR из библиотеки SWR. Здесь объявляется URL API, из которого вы хотите получить данные, и функция для получения этих данных.

Функция fetcher используется здесь для преобразования данных в JSON. Она получает полученные данные в качестве аргумента и возвращает что-то.

Обратите внимание, что здесь я использую оператор Rest ((...args)), поскольку я не уверен в типе и длине данных, полученных в качестве параметра, поэтому я копирую все, прежде чем передать их снова в качестве аргумента методу fetch, предоставляемому useSWR, который преобразует данные в JSON и возвращает их.

Таким образом, fetcher и url API теперь можно передавать в качестве параметров хуку useSWR. Теперь он может выполнить запрос и вернуть два состояния: полученные данные и состояние ошибки. А data: result - это то же самое, что и data.result, мы используем деструктуризацию объекта для извлечения результата из данных.

С возвращаемыми значениями мы можем проверить, успешно ли получены данные, а затем просмотреть их в цикле. И для каждого пользователя использовать компонент Pokemon для отображения.

Теперь у нас есть данные и мы передаем их компоненту Pokemon, пришло время обновить Pokemon.js, чтобы он мог получать и отображать данные.

Создание компонента pokemon

import React from 'react'
import useSWR from 'swr'

const fetcher = (...args) => fetch(...args).then((res) => res.json())

export const Pokemon = ({ pokemon }) => {
  const { name } = pokemon
  const url = 'https://pokeapi.co/api/v2/pokemon/' + name

  const { data, error } = useSWR(url, fetcher)

  if (error) return <h1>Something went wrong!</h1>
  if (!data) return <h1>Loading...</h1>

  return (
    <div className='Card'>
      <span className='Card--id'>#{data.id}</span>
      <img
        className='Card--image'
        src={data.sprites.front_default}
        alt={name}
      />
        <h1 className='Card--name'>{name}</h1>
        <span className='Card--details'>
          {data.types.map((poke) => poke.type.name).join(', ')}
        </span>
    </div>
  )
}

Здесь у нас есть компонент, который получает данные об одном покемоне от API и отображает их. Однако полученные данные не содержат всех необходимых полей, поэтому мы должны сделать еще один запрос к API, чтобы получить полный объект покемона.

Как видите, мы используем тот же процесс для получения данных, даже если на этот раз мы добавим имя покемона к URL.

Кстати, если вы не знакомы с деструктуризацией, ({ pokemon }) - это то же самое, что получение реквизита и доступ к объекту pokemon с помощью props.pokemon. Это просто сокращение для извлечения значений из объектов или массивов.

После этого, если вы перейдете в корневую папку проекта и выполните в терминале следующую команду:

yarn start

Или если вы используете npm:

npm start

Отлично! Теперь мы можем получать удаленные данные с помощью SWR. Однако эта настройка является локальной и может быть немного избыточной, поскольку вы уже видите, что App.js и Pokemon.js используют одну и ту же функцию fetcher для выполнения одних и тех же действий.

Но, к счастью, пакет поставляется с удобным провайдером SWRConfig, который помогает настроить SWR глобально. Это компонент-обертка, который позволяет дочерним компонентам использовать глобальную конфигурацию и, следовательно, функцию fetcher.

Чтобы настроить SWR глобально, нам нужно обновить файл index.js, поскольку именно в нем компонент App отображается с помощью React DOM. Если хотите, вы можете использовать SWRConfig непосредственно в файле App.js.

Настройка swr глобально

import React from 'react'
import ReactDOM from 'react-dom'
import { SWRConfig } from 'swr'
import App from './App'
import './index.css'

const fetcher = (...args) => fetch(...args).then((res) => res.json())

ReactDOM.render(
  <React.StrictMode>
    <SWRConfig value={{ fetcher }}>
      <App />
    </SWRConfig>
  </React.StrictMode>,
  document.getElementById('root')
)

Как вы видите, мы начинаем с импорта SWRConfig, который является провайдером, который должен обернуть высший компонент или просто часть вашего приложения React, которая должна использовать функции SWR. Он принимает в качестве props значение, которое ожидает объект config. Вы можете передать более одного свойства объекту config, здесь мне нужна только функция для получения данных.

Теперь, вместо того чтобы объявлять функцию fetcher в каждом файле, мы создадим ее здесь и передадим ее в качестве значения в SWRConfig. Таким образом, мы можем получать данные на любом уровне нашего приложения без создания еще одной функции и, следовательно, избежать избыточности.

Кроме того, fetcher равнозначен fetcher: fetcher, это просто синтаксический сахар, предложенный ES6. С этим изменением нам нужно обновить наши компоненты для использования глобальной конфигурации.

Используя глобальную конфигурацию swr

import React from 'react'
import useSWR from 'swr'
import { Pokemon } from './components/Pokemon'

const url = 'https://pokeapi.co/api/v2/pokemon'

function App() {
  const { data: result, error } = useSWR(url)

  if (error) return <h1>Something went wrong!</h1>
  if (!result) return <h1>Loading...</h1>

  return (
    <main className='App'>
      <h1>Pokedex</h1>
      <div>
        {result.results.map((pokemon) => (
          <Pokemon key={pokemon.name} pokemon={pokemon} />
        ))}
      </div>
    </main>
  )
}

export default App

Теперь нам нужно только передать url в useSWR, вместо передачи url и метода fetcher. Давайте также немного подправим компонент Pokemon.

import React from 'react'
import useSWR from 'swr'

export const Pokemon = ({ pokemon }) => {
  const { name } = pokemon
  const url = 'https://pokeapi.co/api/v2/pokemon/' + name

  const { data, error } = useSWR(url)

  if (error) return <h1>Something went wrong!</h1>
  if (!data) return <h1>Loading...</h1>

  return (
    <div className='Card'>
      <span className='Card--id'>#{data.id}</span>
        <img
          className='Card--image'
          src={data.sprites.front_default}
          alt={name}
        />
        <h1 className='Card--name'>{name}</h1>
        <span className='Card--details'>
          {data.types.map((poke) => poke.type.name).join(', ')}
        </span>
    </div>
  )
}

Вы уже видите, что у нас больше нет функции fetcher, благодаря глобальной конфигурации, которая передает функцию useSWR под капотом.

Теперь вы можете использовать глобальную функцию fetcher везде в своем приложении. Единственное, что нужно хуку useSWR для получения удаленных данных, - это URL.

Однако мы можем еще больше усовершенствовать настройку, создав пользовательский хук, чтобы не объявлять URL снова и снова, а просто передать в качестве параметра путь.

Расширенная настройка путем создания пользовательского хука

Для этого необходимо создать новый файл в корне проекта с именем useRequest.js (вы можете назвать его как угодно) и добавить в него следующий блок кода.

import useSwr from 'swr'

const baseUrl = 'https://pokeapi.co/api/v2'

export const useRequest = (path, name) => {
  if (!path) {
    throw new Error('Path is required')
  }

  const url = name ? baseUrl + path + '/' + name : baseUrl + path
  const { data, error } = useSwr(url)

  return { data, error }
}

Здесь у нас есть функция, которая получает путь и, по желанию, имя и добавляет их к базовому URL для создания полного URL. Затем она проверяет, получен ли параметр имени или нет, и обрабатывает его соответствующим образом.

Затем этот URL передается в качестве параметра хуку useSWR, чтобы иметь возможность получить удаленные данные и вернуть их. Если же путь не передан, выдается ошибка.

Отлично! Теперь нам нужно немного подправить компоненты, чтобы использовать наш пользовательский хук.

import React from 'react'
import { useRequest } from './useRequest'
import './styles.css'
import { Pokemon } from './components/Pokemon'

function App() {
  const { data: result, error } = useRequest('/pokemon')

  if (error) return <h1>Something went wrong!</h1>
  if (!result) return <h1>Loading...</h1>

  return (
    <main className='App'>
      <h1>Pokedex</h1>
      <div>
        {result.results.map((pokemon) => (
          <Pokemon key={pokemon.name} pokemon={pokemon} />
        ))}
      </div>
    </main>
  )
}

export default App

Теперь, вместо использования хука SWR, мы используем пользовательский хук, созданный на его основе, и передаем, как и ожидалось, путь в качестве аргумента. Теперь все будет работать как раньше, но с гораздо более чистой и гибкой конфигурацией.

Давайте также обновим компонент Pokemon.

import React from 'react'
import { useRequest } from '../useRequest'

export const Pokemon = ({ pokemon }) => {
  const { name } = pokemon
  const { data, error } = useRequest('/pokemon', name)

  if (error) return <h1>Something went wrong!</h1>
  if (!data) return <h1>Loading...</h1>

  return (
    <div className='Card'>
      <span className='Card--id'>#{data.id}</span>
      <img
        className='Card--image'
        src={data.sprites.front_default}
        alt={name}
      />
      <h1 className='Card--name'>{name}</h1>
      <span className='Card--details'>
        {data.types.map((poke) => poke.type.name).join(', ')}
      </span>
    </div>
  )
}

Вы уже видите, как наш пользовательский хук упрощает работу и делает ее более гибкой. Здесь нам просто нужно передать дополнительно имя покемона, которого нужно получить, в useRequest, и он все сделает за нас.

Надеюсь, вам понравится эта классная библиотека. Однако нам еще есть что открыть для себя, ведь SWR предлагает так много возможностей, и одна из них - useSWRPages, которая представляет собой хук для удобной постраничной обработки данных. Итак, давайте используем этот крючок в проекте.

Размещение наших данных по страницам с использованием SWRPages

SWR позволяет нам легко разбивать данные на страницы и запрашивать только часть данных, а при необходимости повторно получать данные для показа на следующей странице.

Теперь создадим новый файл в корне проекта usePagination.js и используем его в качестве пользовательского хука для постраничной публикации.

import React from 'react'
import useSWR, { useSWRPages } from 'swr'
import { Pokemon } from './components/Pokemon'

export const usePagination = (path) => {
  const { pages, isLoadingMore, loadMore, isReachingEnd } = useSWRPages(
    'pokemon-page',
    ({ offset, withSWR }) => {
      const url = offset || `https://pokeapi.co/api/v2${path}`
      const { data: result, error } = withSWR(useSWR(url))

      if (error) return <h1>Something went wrong!</h1>
      if (!result) return <h1>Loading...</h1>

      return result.results.map((pokemon) => (
        <Pokemon key={pokemon.name} pokemon={pokemon} />
      ))
    },
    (SWR) => SWR.data.next,
    []
  )

  return { pages, isLoadingMore, loadMore, isReachingEnd }
}

Как вы видите, здесь мы начинаем с импорта useSWRPages, который является помощником, позволяющим легко размещать данные на страницах. Он получает 4 аргумента: ключ запроса pokemon-page, который также используется для кэширования, функцию для получения данных, которая возвращает компонент, если данные успешно получены, и еще одну функцию, которая принимает объект SWR и запрашивает данные со следующей страницы, а также массив зависимостей.

После получения данных функция useSWRPages возвращает несколько значений, но здесь нам нужны 4 из них: страницы, которые являются компонентом, возвращаемым вместе с данными, функция isLoadingMore, которая проверяет, получены ли данные в настоящее время, функция loadMore, которая помогает получить больше данных, и метод isReachingEnd, который определяет, есть ли еще данные для получения или нет.

Теперь у нас есть пользовательский хук, который возвращает необходимые значения для постраничной обработки данных, и мы можем перейти к файлу App.js и немного подправить его.

import React from 'react'
import { usePagination } from './usePagination'
import './styles.css'

export default function App() {
  const { pages, isLoadingMore, loadMore, isReachingEnd } = usePagination(
    '/pokemon'
  )

  return (
    <main className='App'>
      <h1>Pokedex</h1>
      <div>{pages}</div>
      <button
        onClick={loadMore}
        disabled={isLoadingMore || isReachingEnd}
      >
        Load more...
      </button>
    </main>
  )
}

После импорта хука usePagination мы можем передавать путь в качестве параметра и получать возвращаемые значения. А поскольку pages - это компонент, нам не нужно циклически просматривать данные или что-то в этом роде.

Далее мы используем функцию loadMore на кнопке, чтобы получить больше данных и отключить ее, если операция получения не завершена или если нет данных для получения.

Отлично! С этими изменениями мы можем теперь перейти в корень проекта и запустить сервер с помощью этой команды для предварительного просмотра нашего приложения.

yarn start

Или если вы используете npm:

npm start

Вы должны увидеть, что данные успешно получены, и если вы нажмете на кнопку, SWR будет получать новые данные.

До сих пор мы видели на практике библиотеку SWR, и я надеюсь, что вы находите в ней пользу. Однако у нее еще есть некоторые возможности. Давайте погрузимся в эти функции в следующем разделе.

Другие возможности SWR

В библиотеке SWR есть множество удобных вещей, которые упрощают создание приложений на React.

Focus Revalidation

Это функция, которая позволяет обновлять или перепроверять точные данные при перефокусировке страницы или переключении между вкладками. По умолчанию эта функция включена, но вы можете отключить ее, если она не соответствует вашим потребностям. Это может быть полезно, особенно если у вас есть данные с высокой частотой обновления.

Refetch on Interval

Библиотека SWR позволяет повторно получать данные через определенный промежуток времени. Это может быть удобно, когда ваши данные меняются с высокой скоростью или вам нужно сделать новый запрос, чтобы получить новую информацию из базы данных.

Локальная мутация

С помощью SWR вы можете установить временное локальное состояние, которое будет автоматически обновляться при получении новых данных (ревалидации). Эта функция особенно актуальна, когда вы имеете дело с подходом Offline-first, она помогает легко обновлять данные.

Восстановление позиции прокрутки

Эта функция очень удобна, особенно когда речь идет о работе с огромными списками. Она позволяет восстановить положение прокрутки после возврата на страницу. И в любом случае это повышает удобство использования вашего приложения.

Зависимая выборка

SWR позволяет получать данные, которые зависят от других данных. Это означает, что он может получить данные A, а после завершения этой операции использовать их для получения данных B, избегая при этом водопадов. И эта функция помогает, когда у вас есть реляционные данные.

Таким образом, SWR помогает повысить удобство работы пользователя в любом вопросе. У него больше возможностей, и во многих случаях его лучше использовать вместо Fetch API или библиотеки Axios.

Заключение

В этой статье мы увидели, почему SWR - это потрясающая библиотека. Она позволяет удаленно получать данные с помощью React Hooks и помогает упростить некоторые расширенные функции, такие как пагинация, кэширование данных, повторная выборка по интервалу, восстановление позиции прокрутки и так далее. SWR также не зависит от бэкенда, что означает, что он может получать данные из любых API или баз данных. В целом, SWR значительно повышает удобство работы с React-приложениями, у него большое будущее, и вы должны следить за ним или лучше использовать его в своем следующем React-приложении.

Вы можете посмотреть готовый проект в прямом эфире здесь.

Спасибо за прочтение!


Teacher Army - это команда опытных наставников, готовых помочь вам с особенностями работы с SWR. Если вам нужна помощь во фронтенд-разработке, просто перейдите по ссылке и оставьте заявку.

Мы с удовольствием подберем для вас подходящего ментора по программированию, который проведет с вами индивидуальную консультацию и объяснит любую тему, связанную с SWR или фронтенд-разработкой.

Оригинальная статья

Перевела Дарья Крещёнова

27.11.2023