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