import { Callout } from 'nextra/components'

# Кеш

<Callout>
  Обновитесь до последней версии (≥ 1.0.0), чтобы использовать этот функционал.
</Callout>

<Callout type='warning'>
  В большинстве случаев вы не должны напрямую _писать_ в кеш, поскольку это
  может вызвать неопределенное поведение SWR. Если вам нужно вручную изменить
  ключ, рассмотрите возможность использования API SWR.

См. также: [Мутация](/docs/mutation),
[Сброс кеша между тестами](#сброс-кеша-между-тестами).

</Callout>

По умолчанию SWR использует глобальный кеш для хранения и обмена данными между
всеми компонентами. Но вы также можете настроить это поведение с помощью опции
`provider` в `SWRConfig`.

Cache providers are intended to enable SWR with more customized storages.
Провайдеры кеша предназначены для включения SWR с более индивидуализированными
хранилищами.

## Провайдер кеша

Поставщик кеша — это объект типа Map, который соответствует следующему
определению TypeScript (которое может быть импортировано из `swr`):

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

Например, экземпляр
[JavaScript Map](https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Map)
можно напрямую использовать в качестве провайдера кеша для SWR.

## Создание провайдера кеша

Опция `provider` в `SWRConfig` получает функцию, которая возвращает
[провайдер кеша](#провайдер-кеша). Затем провайдер будет использоваться всеми
хукамми SWR в пределах `SWRConfig`. Например:

```jsx
import useSWR, { SWRConfig } from 'swr'

function App() {
  return (
    <SWRConfig value={{ provider: () => new Map() }}>
      <Page />
    </SWRConfig>
  )
}
```

Все хуки SWR внутри `<Page/>` будут читать и писать из этого экземпляра Map. Вы
также можете использовать другие реализации поставщика кеша для вашего
конкретного случая использования.

<Callout>
  В приведённом выше примере, когда компонент `<App />` повторно монтируется,
  провайдер также будет повторно создан. Провайдеры кеша должны быть размещены
  выше в дереве компонентов или вне рендеринга.
</Callout>

import { Cache } from '@components/diagrams/cache'

<div className="my-8">
  <Cache />
</div>

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

<Callout type='warning'>
  Если используется провайдер кеша, глобальный `mutate` **не** будет работать для хуков SWR в пределе
  этого `<SWRConfig>`. Пожалуйста, используйте [это](#доступ-к-текущему-провайдеру-кеша) взамен.
</Callout>

## Доступ к текущему провайдеру кеша

Находясь внутри компонента React, вам нужно использовать хук
[`useSWRConfig`](/docs/global-configuration#доступ-к-глобальным-конфигурациям),
чтобы получить доступ к текущему провайдеру кеша, а также к другим
конфигурациям, включая `mutate`:

```jsx
import { useSWRConfig } from 'swr'

function Avatar() {
  const { cache, mutate, ...extraConfig } = useSWRConfig()
  // ...
}
```

Если он не находится под каким-либо `<SWRConfig>`, он вернет конфигурации по
умолчанию.

## Экспериментально: Расширение провайдера кеша

<Callout emoji="🧪">
  Это экспериментальный функционал, его поведение может измениться в будущих
  обновлениях.
</Callout>

Когда несколько компонентов `<SWRConfig>` вложены, провайдер кеша может быть
расширен.

Первым аргументом для функции `provider` является поставщик кеша верхнего уровня
`<SWRConfig>` (или кеш по умолчанию, если нет родительского `<SWRConfig>`), вы
можете использовать его для расширения провайдера кеша:

```jsx
<SWRConfig value={{ provider: cache => newCache }}>...</SWRConfig>
```

## Примеры

### Мутация множества ключей из регулярных выражений (RegEx)

Благодаря гибкости API кеш-провайдера вы даже можете создать помощник «частичной
мутации».

В приведенном ниже примере `matchMutate` может принимать регулярное выражение в
качестве ключа и использоваться для мутации тех, кто соответствует этому
шаблону.

```js
function useMatchMutate() {
  const { cache, mutate } = useSWRConfig()
  return (matcher, ...args) => {
    if (!(cache instanceof Map)) {
      throw new Error(
        'matchMutate требует, чтобы провайдер кеша был экземпляром Map'
      )
    }

    const keys = []

    for (const key of cache.keys()) {
      if (matcher.test(key)) {
        keys.push(key)
      }
    }

    const mutations = keys.map(key => mutate(key, ...args))
    return Promise.all(mutations)
  }
}
```

Затем внутри вашего компонента:

```jsx
function Button() {
  const matchMutate = useMatchMutate()
  return (
    <button onClick={() => matchMutate(/^\/api\//)}>
      Ревалидировать все ключи, начинающиеся на "/api/"
    </button>
  )
}
```

<Callout>
  Обратите внимание, что данный пример требует, чтобы провайдер кеша был
  экземпляром Map.
</Callout>

### Постоянный кеш на основе LocalStorage

Возможно, вы захотите синхронизировать свой кеш с `localStorage`. Вот пример
реализации:

```jsx
function localStorageProvider() {
  // При инициализации мы восстанавливаем данные из `localStorage` в Map.
  const map = new Map(JSON.parse(localStorage.getItem('app-cache') || '[]'))

  // Перед выгрузкой приложения мы записываем все данные обратно в `localStorage`.
  window.addEventListener('beforeunload', () => {
    const appCache = JSON.stringify(Array.from(map.entries()))
    localStorage.setItem('app-cache', appCache)
  })

  // Мы по-прежнему используем map для записи и чтения для производительности.
  return map
}
```

Затем используйте его как провайдер:

```jsx
<SWRConfig value={{ provider: localStorageProvider }}>
  <App />
</SWRConfig>
```

<Callout>
  В качестве улучшения вы также можете использовать кеш памяти в качестве буфера
  и периодически записывать в `localStorage`. Вы также можете реализовать
  аналогичный многоуровневый кеш с помощью IndexedDB или WebSQL.
</Callout>

### Сброс кеша между тестами

При тестировании приложения вы можете сбросить кеш SWR между тестами. Вы можете
просто обернуть ваше приложение пустым провайдером кеша. Вот пример с Jest:

```jsx
describe('тестирование', async () => {
  it('тестовый пример', async () => {
    render(
      <SWRConfig value={{ provider: () => new Map() }}>
        <App />
      </SWRConfig>
    )
  })
})
```

### Доступ к кешу

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

```jsx
const { cache } = useSWRConfig()

cache.get(key) // Получить текущие данные для ключа.
cache.clear() // ⚠️ Очистить весь кеш. SWR проведёт ревалидацию при повторном рендеринге.
```
