Skip to content
Представляем SWR 2.0

Представляем SWR 2.0

9 декабря 2022 г. под авторством Shu DingJiachi LiuToru KobayashiYixuan Xu

Мы рады объявить о выпуске SWR 2.0, популярной библиотеки React для получения данных, которая позволяет компонентам получать, кешировать и изменять данные и поддерживать пользовательский интерфейс в актуальном состоянии с учетом изменений в этих данных с течением времени.

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

Мутации и Оптимистичный UI

useSWRMutation

Мутация является важной частью процесса выборки данных. Они позволяют вносить изменения в ваши данные как локально, так и удаленно. Наш существующий API mutate позволяет вам повторно ревалидировать и изменять ресурсы вручную. В SWR 2.0 новый хук useSWRMutation еще больше упрощает удаленное изменение данных с помощью декларативного API. Вы можете настроить мутацию с помощью хука, а затем активировать ее позже:

import useSWRMutation from 'swr/mutation'
 
async function sendRequest(url, { arg }) {
  return fetch(url, {
    method: 'POST',
    body: JSON.stringify(arg)
  })
}
 
function App() {
  const { trigger, isMutating } = useSWRMutation('/api/user', sendRequest)
 
  return (
    <button
      disabled={isMutating}
      onClick={() => trigger({ username: 'johndoe' })}
    >{
      isMutating ? 'Создаётся...' : 'Создать пользователя'
    }</button>
  )
}

В приведенном выше примере определена мутация sendRequest, которая влияет на ресурс '/api/user'. В отличие от useSWR, useSWRMutation не делает запрос сразу после рендеринга. Вместо этого он возвращает trigger-функцию, которую позже можно вызвать для ручного запуска мутации.

Функция sendRequest будет вызываться при нажатии кнопки с дополнительным аргументом {username: 'johndoe' }. Значение isMutating будет установлено на true, пока мутация не завершится.

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

  • Оптимистичное обновление пользовательского интерфейса во время мутации данных
  • Автоматическое откатывание при сбое мутации
  • Избежание любых потенциальных состояний гонки между useSWR и другими мутациями того же ресурса
  • Заполнение кеша useSWR после завершения мутации
  • ...

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

Оптимистичный UI

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

Допустим, у нас есть API, который добавляет новую задачу в список задач и отправляет ее на сервер:

await addNewTodo('Новый элемент')

В нашем пользовательском интерфейсе мы используем хук useSWR для отображения списка дел с кнопкой «Добавить новый элемент», которая запускает этот запрос и просит SWR повторно получить данные при помощи mutate():

const { mutate, data } = useSWR('/api/todos')
 
return <>
  <ul>{/* Отображение данных */}</ul>
 
  <button onClick={async () => {
    await addNewTodo('Новый элемент')
    mutate()
  }}>
    Добавить новый элемент
  </button>
</>

Однако запрос await addNewTodo(...) может быть очень медленным. Когда он разворачивается, пользователи по-прежнему видят старый список, даже если мы уже знаем, как будет выглядеть новый список. С новой опцией optimisticData мы можем отображать новый список оптимистично, прежде чем сервер ответит:

const { mutate, data } = useSWR('/api/todos')
 
return <>
  <ul>{/* Отображение данных */}</ul>
 
  <button onClick={() => {
    mutate(addNewTodo('Новый элемент'), {
      optimisticData: [...data, 'Новый элемент'],
    })
  }}>
    Добавить новый элемент
  </button>
</>

SWR немедленно обновит data значением optimisticData, а затем отправит запрос на сервер. Как только запрос завершится, SWR ревалидирует ресурс, чтобы убедиться, что он самый последний.

Как и во многих API, если запрос addNewTodo(...) возвращает нам последние данные с сервера, мы также можем напрямую показать этот результат (вместо того, чтобы начинать новую ревалидацию)! Появилась новая опция populateCache, сообщающая SWR о необходимости обновить локальные данные теми, что возвращает мутация:

const { mutate, data } = useSWR('/api/todos')
 
return <>
  <ul>{/* Отображение данных */}</ul>
 
  <button onClick={() => {
    mutate(addNewTodo('Новый элемент'), {
      optimisticData: [...data, 'Новый элемент'],
      populateCache: true,
    })
  }}>
    Добавить новый элемент
  </button>
</>

В то же время нам не нужна после этого ещё одна ревалидация, так как данные ответа исходят из источника правды, мы можем отключить ее с помощью опции revalidate:

const { mutate, data } = useSWR('/api/todos')
 
return <>
  <ul>{/* Отображение данных */}</ul>
 
  <button onClick={() => {
    mutate(addNewTodo('Новый элемент'), {
      optimisticData: [...data, 'Новый элемент'],
      populateCache: true,
      revalidate: false,
    })
  }}>
    Добавить новый элемент
  </button>
</>

Наконец, если addNewTodo(...) завершается ошибкой с исключением, мы можем откатить оптимистичные данные ([...data, 'Новый элемент']), которые мы только что установили, установив для rollbackOnError значение true (что также является опцией по умолчанию). Когда это происходит, SWR вернет data к предыдущему значению.

const { mutate, data } = useSWR('/api/todos')
 
return <>
  <ul>{/* Отображение данных */}</ul>
 
  <button onClick={() => {
    mutate(addNewTodo('Новый элемент'), {
      optimisticData: [...data, 'Новый элемент'],
      populateCache: true,
      revalidate: false,
      rollbackOnError: true,
    })
  }}>
    Добавить новый элемент
  </button>
</>

Все эти API также поддерживаются в новом хуке useSWRMutation. Чтобы узнать о них больше, вы можете ознакомиться с нашей документацией. И вот демонстрация, показывающая такое поведение:

Оптимистичный UI с автоматическим откатом при ошибке

Мутация нескольких ключей

Глобальный API-интерфейс mutate теперь поддерживает функцию фильтрации, с помощью которой вы можете изменять или повторно проверять определенные ключи. Это будет полезно для таких случаев использования, как аннулирование всех кешированных данных. Чтобы узнать больше, вы можете прочитать про Мутацию нескольких ключей в документации.

import { mutate } from 'swr'
// Или из хука, если вы настроили поставщика кеша:
// { mutate } = useSWRConfig()
 
// Мутация одного ресурса
mutate(key)
 
// Мутация нескольких ресурсов и очистка кеша (установите значение undefined)
mutate(
  key => typeof key === 'string' && key.startsWith('/api/item?id='),
  undefined,
  { revalidate: false }
)

SWR DevTools

SWRDevTools (opens in a new tab) — это расширение для браузера, помогающее отлаживать кеш SWR и результаты выборки. Посетите наш раздел devtools, чтобы узнать, как использовать инструменты разработчика в своем приложении.

Предзагрузка данных

Предварительная загрузка данных может значительно улучшить взаимодействие с пользователем. Если вы знаете, что ресурс будет использоваться в приложении позже, вы можете использовать новый API preload, чтобы начать извлекать его раньше:

import useSWR, { preload } from 'swr'
 
const fetcher = (url) => fetch(url).then((res) => res.json())
 
// Вы можете вызвать функцию предзагрузки в любом месте
preload('/api/user', fetcher)
 
function Profile() {
  // Компонент, который фактически использует данные:
  const { data, error } = useSWR('/api/user', fetcher)
  // ...
}
 
export function Page () {
  return <Profile/>
}

В этом примере API предзагрузки вызывается в глобальной области видимости. Это означает, что мы начинаем предварительно загружать ресурс ещё до того, как React начнет что-либо отображать. И когда компонент Profile рендерится, данные, вероятно, уже могут быть доступны. Если запрос всё ещё выполняется, хук useSWR будет повторно использовать текущий запрос на предварительную загрузку вместо запуска нового.

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

isLoading

isLoading — это новое состояние, возвращаемое useSWR, которое указывает, если запрос всё ещё выполняется и данные ещё не загружены. Раньше состояние isValidating представляло собой как начальное состояние загрузки, так и состояние повторной проверки, поэтому нам приходилось проверять, являются ли data и error undefined, чтобы определить, было ли это состояние начальной загрузки.

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

import useSWR from 'swr'
 
function Profile() {
  const { data, isLoading } = useSWR('/api/user', fetcher)
 
  if (isLoading) return <div>загрузка...</div>
  return <div>привет {data.name}!</div>
}

Обратите внимание, что isValidating по-прежнему присутствует, поэтому вы все равно можете использовать его для отображения индикатора загрузки для ревалидаций.

📝

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

Сохранение предыдущего состояния

Опция keepPreviousData — это новое дополнение, позволяющее вам сохранить данные, которые были получены ранее. Это значительно улучшает UX, когда вы извлекаете данные на основе действий пользователя, происходящих в режиме реального времени, например, с функцией поиска в реальном времени, где key ресурса постоянно меняется:

function Search() {
  const [search, setSearch] = React.useState('');
 
  const { data, isLoading } = useSWR(`/search?q=${search}`, fetcher, {
    keepPreviousData: true
  })
 
  return (
    <div>
      <input
        type="text"
        value={search}
        onChange={(e) => setSearch(e.target.value)}
        placeholder="Поиск..."
      />
 
      <div className={isLoading ? "загрузка" : ""}>
        {data?.products.map(item => <Product key={item.id} name={item.name} />)
      </div>
    </div>
  );
}
Сохранять предыдущие результаты поиска, если включен параметр keepPreviousData

Проверьте код в CodeSandbox (opens in a new tab), и вы можете узнать о нем больше здесь.

Расширение конфигураций

SWRConfig теперь может принимать функцию в качестве значения. Если у вас есть несколько уровней <SWRConfig>, внутренний получает родительскую конфигурацию и возвращает новую. Это изменение делает настройку SWR более гибкой в большой кодовой базе. Дополнительную информацию можно найти здесь.

<SWRConfig
  value={parentConfig => ({
    dedupingInterval: parentConfig.dedupingInterval * 5,
    refreshInterval: 100,
  })}
>
  <Page />
</SWRConfig>

Улучшенная поддержка React 18

SWR обновил свой внутренний код, чтобы использовать API useSyncExternalStore и startTransition в React 18. Это обеспечивает более высокую согласованность при одновременном рендеринге пользовательского интерфейса. Это изменение не требует каких-либо изменений пользовательского кода, и все разработчики получат от него непосредственную выгоду. Прокладки включены для React 17 и ниже.

SWR 2.0 и все новые функции по-прежнему совместимы с React 16 и 17.

Руководство по миграции

Fetcher больше не принимает несколько аргументов

key теперь передаётся как один аргумент.

- useSWR([1, 2, 3], (a, b, c) => {
+ useSWR([1, 2, 3], ([a, b, c]) => {
  assert(a === 1)
  assert(b === 2)
  assert(c === 3)
})

Глобальная мутация больше не принимает функцию getKey

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

- mutate(() => '/api/item') // a function to return a key
+ mutate('/api/item')       // to mutate the key, directly pass it

Новое обязательное свойство keys() для интерфейса кеша

Когда вы используете свою собственную реализацию кеша, для интерфейса Cache теперь требуется метод keys(), который возвращает все ключи в объекте кеша, аналогично экземплярам Map в JavaScript.

interface Cache<Data> {
  get(key: string): Data | undefined
  set(key: string, value: Data): void
  delete(key: string): void
+ keys(): IterableIterator<string>
}

Изменена внутренняя структура кэша

Внутренняя структура данных кэша будет представлять собой объект, содержащий все текущие состояния.

- assert(cache.get(key) === data)
+ assert(cache.get(key) === { data, error, isValidating })
 
// getter
- cache.get(key)
+ cache.get(key)?.data
 
// setter
- cache.set(key, data)
+ cache.set(key, { ...cache.get(key), data })
🚨

Вы не должны писать в кеш напрямую, это может привести к неопределённому поведению.

SWRConfig.default переименован в SWRConfig.defaultValue

SWRConfig.defaultValue — это свойство для доступа к конфигурации SWR по умолчанию.

- SWRConfig.default
+ SWRConfig.defaultValue

Тип InfiniteFetcher переименован в SWRInfiniteFetcher

- import type { InfiniteFetcher } from 'swr/infinite'
+ import type { SWRInfiniteFetcher } from 'swr/infinite'

Избегайте Задержку на сервере

Если вы хотите использовать suspense: true с SWR на сервера, включая предварительный рендеринг в Next.js, то вы должны указать исходные данные через fallbackData или fallback. Сегодня это означает, что вы не можете использовать Suspense для получения данных на сервере. Два других варианта: полностью извлекать данные на стороне клиента или заставить фреймворк извлекать данные за вас (как это делает getStaticProps в Next.js).

ES2018 в качестве цели сборки (Build Target)

Если вы хотите поддерживать IE 11, вы должны использовать таргет ES5 в своём фреймворке или сборщике. Это изменение позволило повысить производительность SSR и сохранить небольшой размер бандла.

Журнал изменений

Прочтите полный журнал изменений на GitHub (opens in a new tab).

Будущее и спасибо!

С новым выпуском Next.js 13 (opens in a new tab) мы видим много интересных новых вещей, а также сдвиги парадигмы в экосистеме React: Серверные Компоненты React (opens in a new tab), потоковая SSR, асинхронные компоненты (opens in a new tab) и хук use (opens in a new tab). Многие из них связаны с выборкой данных, а некоторые из них имеют варианты пересекающиеся с использования с SWR.

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

Мы хотим поблагодарить каждого из 143 (opens in a new tab) контрибютеров (+ 106 (opens in a new tab) контрибютеров документации), а также тех, кто помогает нам или оставили отзывы. Особая благодарность Toru Kobayashi (opens in a new tab) за всю его работу над DevTools и документацией — без тебя мы бы не справились!