EMZETT.
Войти

React.cache()

Коротко: Функция React, кэширующая результат асинхронного вызова (например, запроса к базе данных) в рамках одного запроса — если вы вызываете одну и ту же функцию с теми же аргументами несколько раз в одном запросе, реальная работа выполняется лишь раз.

Подробнее: Важно: кэш действует только в течение одного серверного запроса, а не между разными пользователями или запросами (нет глобального/общего кэша). Типичный случай: функция вроде getCurrentUser() часто вызывается в нескольких независимых местах за один просмотр страницы (например, раз в шапке, раз в самой странице) — без React.cache() это означает несколько одинаковых обращений к базе данных туда и обратно на один просмотр страницы.

Наш контекст: Это была правдоподобная главная причина того, что сервер Emzett на Vercel время от времени падал, особенно при нескольких одновременных посетителях — getCurrentUser()/getSetting() выполнялись дважды на 11 из 12 страниц, каждый раз с полным обращением к базе данных. После оборачивания в React.cache() два вызова — проверено вживую — превратились в один реальный доступ к базе данных.

Подробности

Как это работает: React.cache() запоминает для каждого запроса (технически: для каждого прохода серверной отрисовки React), какая функция с какими аргументами уже вызывалась, и при втором одинаковом вызове просто возвращает уже вычисленный результат вместо повторного выполнения функции:

import { cache } from "react";
export const getCurrentUser = cache(async () => {
  const session = await getSession();
  if (!session) return null;
  return db.query.users.findFirst({ where: eq(users.id, session.userId) });
});

Важно отличать его от других механизмов кэширования Next.js, звучащих похоже, но делающих иное: кэширование fetch() и теги кэша/revalidateTag кэшируют данные МЕЖДУ страницами и МЕЖДУ запросами (пока не будут явно сброшены), тогда как React.cache() явно дедуплицирует ТОЛЬКО в рамках жизненного цикла одного запроса, а затем отбрасывается — для следующего пользователя или следующего просмотра страницы функция снова работает совершенно обычно.

Почему дедупликация не работает для разных данных

React.cache() распознаёт «одинаковые вызовы» по сравнению переданных аргументов (подобно ключу мемоизации). Если вызвать ту же функцию с РАЗНЫМИ аргументами, дедупликация не применяется — каждый уникальный набор аргументов выполняется один раз на запрос:

await getUserById("abc"); // действительно выполняется
await getUserById("abc"); // из кэша, без обращения к базе данных
await getUserById("xyz"); // другие аргументы -> снова выполняется

Обычно это именно желаемое поведение, но частое заблуждение: тот, кто полагает, что React.cache() объединяет КАЖДОЕ обращение к базе данных в запросе, удивится, когда два чуть различающихся вызова (например, один с необязательным параметром, другой без) всё равно запустят два реальных запроса.

Отличие от useMemo

useMemo — клиентская функция (выполняется в браузере, на компонент, между перерисовками) и предназначена для синхронных вычислений — React.cache() только серверная, создана для асинхронного доступа к данным и дедуплицирует по всей ширине дерева компонентов запроса, а не только внутри одного компонента. Они решают разные задачи и не заменяют друг друга.

См. также: Neon, Neon Auto-Suspend, Теги кэша/revalidateTag