cache¶
Canary
-
cache
предназначен только для использования с React Server Components. Смотрите фреймворки, которые поддерживают React Server Components. -
cache
доступен только в каналах React Canary и experimental. Пожалуйста, убедитесь, что вы понимаете ограничения, прежде чем использоватьcache
в производстве. Узнайте больше о каналах выпуска React здесь.
cache
позволяет кэшировать результат выборки данных или вычислений.
1 |
|
Описание¶
cache(fn)
¶
Вызовите cache
вне каких-либо компонентов, чтобы создать версию функции с кэшированием.
1 2 3 4 5 6 7 8 9 |
|
При первом вызове getMetrics
с data
, getMetrics
вызовет calculateMetrics(data)
и сохранит результат в кэше. Если getMetrics
будет вызвана снова с теми же data
, она вернет кэшированный результат вместо повторного вызова calculateMetrics(data)
.
Параметры¶
fn
: Функция, для которой вы хотите кэшировать результаты. Функцияfn
может принимать любые аргументы и возвращать любое значение.
Возвращает¶
cache
возвращает кэшированную версию fn
с той же сигнатурой типа. При этом вызов fn
не производится.
При вызове cachedFn
с заданными аргументами сначала проверяется, существует ли кэшированный результат в кэше. Если кэшированный результат существует, он возвращает его. Если нет, он вызывает fn
с аргументами, сохраняет результат в кэше и возвращает его. Единственный раз, когда вызывается fn
, это когда происходит пропуск кэша.
Мемоизация
Оптимизация кэширования возвращаемых значений на основе входных данных известна как мемоизация. Мы называем функцию, возвращаемую из cache
, мемоизированной функцией.
Замечания¶
- React аннулирует кэш для всех мемоизированных функций при каждом запросе сервера.
- Каждый вызов
cache
создает новую функцию. Это означает, что вызовcache
с одной и той же функцией несколько раз будет возвращать разные мемоизированные функции, которые не используют один и тот же кэш. cachedFn
также будет кэшировать ошибки. Еслиfn
выбрасывает ошибку для определенных аргументов, она будет кэширована, и та же ошибка будет повторно выброшена, когдаcachedFn
будет вызвана с теми же аргументами.cache
предназначен только для использования в Серверных компонентах.
Использование¶
Кэширование дорогих вычислений¶
Используйте cache
для пропуска дублирующей работы.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Если один и тот же объект user
отображается и в Profile
, и в TeamReport
, оба компонента могут разделить работу и вызвать calculateUserMetrics
только один раз для этого user
.
Предположим, что первым рендерится Profile
. Он вызовет getUserMetrics
и проверит, есть ли кэшированный результат. Поскольку getUserMetrics
вызывается впервые для этого 'user', произойдет пропуск кэша. Затем getUserMetrics
вызовет calculateUserMetrics
с этим пользователем
и запишет результат в кэш.
Когда TeamReport
отобразит свой список 'users' и достигнет того же самого объекта user
, он вызовет getUserMetrics
и прочитает результат из кэша.
Вызов разных мемоизированных функций будет считывать данные из разных кэшей¶
Чтобы получить доступ к одному и тому же кэшу, компоненты должны вызывать одну и ту же мемоизированную функцию.
1 2 3 4 5 6 7 8 9 10 11 |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
В приведенном выше примере Precipitation
и Temperature
каждый вызывает cache
для создания новой мемоизированной функции с собственным поиском в кэше. Если оба компонента выполняют рендеринг для одного и того же cityData
, они будут выполнять дублирующую работу по вызову calculateWeekReport
.
Кроме того, Temperature
создает новую мемоизированную функцию каждый раз, когда компонент рендерится, что не позволяет разделить кэш.
Чтобы максимизировать количество обращений к кэшу и сократить объем работы, оба компонента должны вызывать одну и ту же мемоизированную функцию для доступа к одному и тому же кэшу. Вместо этого определите мемоизированную функцию в специальном модуле, который можно import
для всех компонентов.
1 2 3 4 5 |
|
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 7 |
|
Здесь оба компонента вызывают одну и ту же мемоизированную функцию, экспортированную из ./getWeekReport.js
, для чтения и записи в один и тот же кэш.
Совместное использование снимка данных¶
Чтобы поделиться снимком данных между компонентами, вызовите cache
с функцией получения данных, например fetch
. Когда несколько компонентов выполняют одну и ту же выборку данных, выполняется только один запрос, а возвращаемые данные кэшируются и разделяются между компонентами. Все компоненты обращаются к одному и тому же снимку данных во время рендеринга сервера.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Если AnimatedWeatherCard
и MinimalWeatherCard
рендерятся для одного и того же города, то они получат один и тот же снимок данных из мемоизированной функции.
Если AnimatedWeatherCard
и MinimalWeatherCard
передают разные аргументы города в getTemperature
, то fetchTemperature
будет вызван дважды, и каждый сайт вызова получит разные данные.
Город действует как ключ кэша.
Асинхронный рендеринг
Асинхронный рендеринг поддерживается только для серверных компонентов.
1 2 3 4 |
|
Предварительная загрузка данных¶
Кэширование длительной выборки данных позволяет запустить асинхронную работу до рендеринга компонента.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
При рендеринге Page
компонент вызывает getUser
, но обратите внимание, что он не использует возвращенные данные. Этот ранний вызов getUser
запускает асинхронный запрос к базе данных, который происходит, пока Page
выполняет другую вычислительную работу и рендерит дочерние страницы.
При рендеринге Profile
мы снова вызываем getUser
. Если первоначальный вызов getUser
уже вернул и кэшировал данные о пользователе, то когда Profile
запрашивает и ждет эти данные, он может просто прочитать их из кэша, не требуя повторного вызова удаленной процедуры. Если первоначальный запрос данных не был завершен, предварительная загрузка данных в этом шаблоне уменьшает задержку в получении данных.
Кэширование асинхронной работы¶
При выполнении асинхронной функции вы получите Promise для этой работы. Promise содержит состояние этой работы (в ожидании, выполнена, не выполнена) и ее конечный результат.
В этом примере асинхронная функция fetchData
возвращает обещание, которое ожидает fetch
.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
При первом вызове getData
обещание, возвращенное из fetchData
, кэшируется. Последующие вызовы будут возвращать то же обещание.
Обратите внимание, что в первом вызове getData
не используется await
, тогда как во втором - используется. await
- это оператор JavaScript, который будет ждать и вернет готовый результат обещания. Первый вызов getData
просто инициирует fetch
для кэширования обещания, чтобы второй getData
мог его просмотреть.
Если ко второму вызову обещание все еще ожидает, то await
сделает паузу для получения результата. Оптимизация заключается в том, что пока мы ждем fetch
, React может продолжать вычислительную работу, тем самым сокращая время ожидания второго вызова.
Если обещание уже выполнено, либо ошибка, либо выполненный результат, await
вернет это значение немедленно. В обоих случаях выигрыш в производительности налицо.
Вызов мемоизированной функции вне компонента не будет использовать кэш¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
React предоставляет доступ к кэшу только для мемоизированной функции в компоненте. При вызове getUser
вне компонента, он по-прежнему будет оценивать функцию, но не будет считывать или обновлять кэш.
Это происходит потому, что доступ к кэшу осуществляется через контекст, который доступен только из компонента.
Когда следует использовать cache
, memo
или useMemo
?¶
Все упомянутые API предлагают мемоизацию, но разница заключается в том, что они предназначены для мемоизации, кто может получить доступ к кэшу и когда их кэш будет аннулирован.
useMemo
¶
В общем случае для кэширования дорогостоящих вычислений в клиентском компоненте при разных рендерах следует использовать useMemo
. Например, для мемоизации преобразования данных внутри компонента.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
В этом примере App
отображает два WeatherReport
с одной и той же записью. Несмотря на то, что оба компонента выполняют одну и ту же работу, они не могут делиться ею. Кэш useMemo
является локальным только для компонента.
Однако useMemo
гарантирует, что если App
перерендерится и объект record
не изменится, каждый экземпляр компонента пропустит работу и использует мемоизированное значение avgTemp
. useMemo
будет кэшировать только последнее вычисление avgTemp
с заданными зависимостями.
cache
¶
В общем случае следует использовать cache
в серверных компонентах для запоминания работы, которая может быть разделена между компонентами.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Если переписать предыдущий пример и использовать cache
, то в этом случае второй экземпляр WeatherReport
сможет пропустить дублирование работы и читать из того же кэша, что и первый WeatherReport
. Еще одним отличием от предыдущего примера является то, что cache
также рекомендуется для мемоизации поиска данных, в отличие от useMemo
, который должен использоваться только для вычислений.
В настоящее время cache
следует использовать только в серверных компонентах, и при запросах к серверу кэш будет аннулирован.
memo
¶
Вы должны использовать memo
для предотвращения повторного рендеринга компонента, если его реквизиты не изменились.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
В этом примере оба компонента MemoWeatherReport
вызовут calculateAvg
при первом рендеринге. Однако если App
перерендерится без изменений в record
, ни один из реквизитов не изменится, и MemoWeatherReport
не перерендерится.
По сравнению с useMemo
, memo
мемоизирует рендеринг компонента на основе реквизитов, а не конкретных вычислений. Как и в случае с useMemo
, компонент с мемоизацией кэширует только последний рендер с последними значениями реквизитов. Как только реквизит меняется, кэш аннулируется и компонент рендерится заново.
Устранение неполадок¶
Моя мемоизированная функция все еще выполняется, даже если я вызывал ее с теми же аргументами¶
См. ранее упомянутые подводные камни
- Вызов разных мемоизированных функций будет считываться из разных кэшей
- Вызов мемоизированной функции вне компонента не будет использовать кэш.
Если ничего из вышеперечисленного не работает, возможно, проблема в том, как React проверяет, существует ли что-то в кэше.
Если ваши аргументы не являются примитивами (например, объекты, функции, массивы), убедитесь, что вы передаете одну и ту же ссылку на объект.
При вызове мемоизированной функции React просмотрит входные аргументы на предмет того, не кэширован ли уже результат. React будет использовать неглубокое равенство аргументов, чтобы определить, есть ли попадание в кэш.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
В данном случае два MapMarker
выглядят так, как будто они выполняют одну и ту же работу и вызывают calculateNorm
с одним и тем же значением {x: 10, y: 10, z:10}
. Несмотря на то, что объекты содержат одинаковые значения, они не являются одной и той же объектной ссылкой, поскольку каждый компонент создает свой собственный объект props
.
React вызовет Object.is
на входе, чтобы проверить, есть ли попадание в кэш.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
Одним из способов решения этой проблемы может быть передача размеров вектора в calculateNorm
. Это работает, потому что сами размеры являются примитивами.
Другим решением может быть передача компоненту самого объекта вектора в качестве параметра. Нам нужно будет передать один и тот же объект обоим экземплярам компонента.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Источник — https://react.dev/reference/react/cache