Сайт использует сookies для хранения данных. Продолжая использовать сайт, вы даёте согласие на работу с этими файлами.

ОК
💻
Технологии
Опубликовано:
14.09.2026
Обновлено:
14.09.2026

Архитектура 4 слоев состояния: Local, URL, Global и Server в React и Next.js

Тимофей Ищенко

Коротко: Разбираем архитектуру 4 слоев состояния: Local, URL, Server и Global State в React и Next.js. Практическое руководство по снижению техдолга и управлению данными.

Одна из главных причин накопления технического долга во фронтенд-приложениях — размытая ответственность при работе с данными. Когда разработчики помещают всю информацию в единый глобальный стор (Redux, Zustand или глобальный React Context), кодовая база быстро теряет предсказуемость. Возникают проблемы рассинхронизации интерфейса, «гонки запросов» (race conditions), сложности со сбросом фильтров и невозможность поделиться прямой ссылкой на нужное состояние страницы.

Архитектура 4 слоев состояния — это системный подход к структурированию данных во фронтенде. Разделение стейта на Local, URL, Global и Server позволяет определить единственный источник правды (Single Source of Truth) для каждого типа данных, упростить поддержку кода и избежать избыточного ререндеринга.


Слой 1. Local State — автономность интерфейсных компонентов

Локальное состояние принадлежит конкретному компоненту и полностью изолировано внутри него. Это данные, которые не влияют на бизнес-логику других частей приложения и уничтожаются при размонтировании (unmount) компонента из DOM-дерева.

Что должно жить локально

  • Состояние открытия и закрытия выпадающих списков (Dropdown), тултипов, аккордеонов.
  • Внутренний ввод в неконтролируемые или изолированные поля формы до момента отправки.
  • Состояния наведения (hover), фокуса, анимации и локальные индикаторы переключения интерфейса.
  • Локальные временные таймеры.

Подъем состояния (Lifting State Up)

Когда двум соседним компонентам требуется доступ к одним и тем же данным, официальная модель React предписывает использовать паттерн «Lifting State Up» — подъем состояния до их ближайшего общего предка.

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

// Пример: изолированное локальное состояние аккордеона
import { useState } from 'react';

export const AccordionItem = ({ title, content }: { title: string; content: string }) => {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div className="border-b">
      <button onClick={() => setIsOpen((prev) => !prev)} className="py-2 font-medium">
        {title}
      </button>
      {isOpen && <div className="py-2 text-gray-600">{content}</div>}
    </div>
  );
};

Слой 2. URL State — забытый источник правды

Адресная строка браузера — это полноценное, встроенное и персистентное хранилище состояния. Игнорирование URL при проектировании фильтров, сортировок и навигации ломает базовый пользовательский опыт в вебе.

Зачем переносить состояние в адресную строку

  1. Шаринг ссылок (Deep Linking): пользователь может скопировать URL со всеми примененными параметрами и отправить его другому человеку.
  2. История браузера: кнопки «Назад» и «Вперед» работают предсказуемо, восстанавливая экран в том виде, в котором он был.
  3. Устойчивость к перезагрузке: при обновлении страницы (F5) данные не сбрасываются в значения по умолчанию.

Сортировка, фильтры и пагинация

В URL-состоянии должны храниться:

  • Поисковые запросы (?q=react).
  • Номера страниц и лимиты пагинации (?page=2&limit=20).
  • Параметры сортировки (?sort=price_desc).
  • Активные табы и выбранные режимы отображения каталога (?view=grid).

Влияние на SSR в Next.js

В Next.js чтение параметров адресной строки осуществляется через searchParams в серверных страницах или хук useSearchParams на клиенте.

Параметры searchParams относятся к request-time API. Значения параметров строки запроса невозможно вычислить на этапе сборки проекта, поэтому чтение searchParams на странице переводит маршрут в режим динамического рендеринга (dynamic rendering) в момент входящего HTTP-запроса.

// Пример: чтение параметров поиска в Next.js App Router
interface PageProps {
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}

export default async function CatalogPage({ searchParams }: PageProps) {
  const resolvedParams = await searchParams;
  const page = Number(resolvedParams.page) || 1;
  const category = (resolvedParams.category as string) || 'all';

  return (
    <main>
      <h1>Каталог (Категория: {category}, Страница: {page})</h1>
    </main>
  );
}

Слой 3. Server State — кэширование и синхронизация данных бэкенда

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

Проблемы ручной синхронизации

Попытка сохранять ответы REST API или GraphQL напрямую в Redux или useState требует ручного написания большого объема инфраструктурного кода:

  • Отслеживание флагов загрузки (isLoading, isError).
  • Управление повторными запросами при потере соединения (retries).
  • Предотвращение гонки запросов (race conditions), когда старый медленный ответ перезаписывает свежие данные.
  • Ручная дедупликация идентичных запросов с разных компонентов.

Инструменты серверного слоя

Для работы с Server State используются специализированные кэширующие библиотеки: TanStack Query (React Query), RTK Query, SWR, а также встроенный механизм кэширования запросов fetch в Next.js.

Эти инструменты берут на себя фоновую инвалидацию (stale-while-revalidate), сборку мусора, очистку неиспользуемого кэша и дедупликацию HTTP-вызовов без загрязнения глобального клиентского хранилища.

// Пример: получение серверного кэша через TanStack Query
import { useQuery } from '@tanstack/react-query';

const fetchUserProfile = async (userId: string) => {
  const res = await fetch(`/api/users/${userId}`);
  if (!res.ok) throw new Error('Ошибка загрузки профиля');
  return res.json();
};

export const UserProfile = ({ userId }: { userId: string }) => {
  const { data, isLoading, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUserProfile(userId),
    staleTime: 1000 * 60 * 5, // Данные считаются свежими 5 минут
  });

  if (isLoading) return <div>Загрузка...</div>;
  if (error) return <div>Произошла ошибка</div>;

  return <div>Пользователь: {data.name}</div>;
};

Слой 4. Global Client State — системные настройки приложения

Глобальное клиентское состояние — это данные, которые инициализируются, изменяются и полностью контролируются браузером, будучи доступными для множества несвязанных между собой компонентов.

После выноса данных бэкенда в Server State и параметров фильтрации в URL State объем чистого клиентского глобального состояния сокращается в разы.

Что должно храниться в глобальном клиентском стейте

  • Настройки интерфейса: текущая цветовая тема (Dark/Light), состояние сворачивания бокового меню (Sidebar).
  • Состояние клиентской сессии и авторизационные метаданные на клиенте.
  • Сложные многошаговые формы (Wizards), если их шаги не дублируются в URL.
  • Корзина покупок во временном неавторизованном сценарии (до отправки на бэкенд).
  • Системные уведомления (Toasts / Flash messages).

Инструменты управления

Для глобального состояния подходят Redux Toolkit или Zustand. Redux обеспечивает строгую предсказуемость за счет централизованных редьюсеров, поддержки экшенов и отладки через Redux DevTools, что критично для крупных корпоративных систем со сложными кросс-модульными сценариями.


Матрица принятия решений: куда положить состояние?

Слой состояния Область видимости Где сохраняется Способ обновления Типичные примеры
Local State Один компонент или изолированная ветка DOM Память компонента (сбрасывается при unmount) useState, useReducer Модальные окна, дропдауны, локальный ввод
URL State Все приложение, доступно внешне Адресная строка браузера, History API Роутер (useRouter, ссылки Link) Фильтры каталога, табы, пагинация, поиск
Server State Все приложение, использующее ресурс Асинхронный кэш в памяти TanStack Query, RTK Query, SWR, Next.js fetch Данные профиля, списки заказов, каталоги API
Global State Все клиентское дерево приложения Клиентский стор в памяти Redux Toolkit, Zustand, Context API Тема оформления, корзина, сайдбар, тосты

Типичные антипаттерны и борьба с техдолгом

1. Дублирование серверных данных в Redux без механизмов инвалидации

Распространенная ошибка — создание редьюсеров usersSlice или ordersSlice, куда через dispatch(setOrders(data)) вручную записывается ответ сервера. В результате в приложении появляются устаревшие данные, которые не обновляются при мутациях на других экранах без ручного написания десятков синхронизирующих экшенов.

2. Изоляция фильтров в локальном useState

Если каталог интернет-магазина сохраняет выбранную категорию или номер страницы исключительно в useState, страница теряет возможность быть отправленной через мессенджер, а нажатие кнопки «Назад» в браузере сбрасывает все примененные параметры.

3. Избыточный Prop Drilling вместо композиции

Передача пропсов через 5–7 промежуточных компонентов часто ошибочно решается выносом стейта в Redux. В большинстве случаев проблема решается композицией компонентов (передачей children или готовых элементов через JSX-пропсы), сохраняя данные на локальном уровне.


FAQ

1. Когда стоит предпочесть URL State вместо Local State?

URL State необходим в тех случаях, когда экран должен восстанавливать свое состояние после перезагрузки страницы, корректно обрабатывать навигацию кнопками «Назад / Вперед» в браузере или когда ссылкой на текущее представление нужно делиться с другими пользователями (например, примененные фильтры и сортировка в каталоге).

2. Зачем нужен TanStack Query или RTK Query, если в проекте уже настроен Redux?

Для серверных данных требуются фоновое обновление (stale-while-revalidate), автоматическая дедупликация одинаковых параллельных запросов, управление кэшем, повторные попытки при сбоях и инвалидация после мутаций. Реализация этих алгоритмов вручную в Redux порождает сотни строк шаблонного кода (boilerplate) и увеличивает вероятность появления багов гонки запросов (race conditions).

3. Всегда ли использование searchParams в Next.js делает страницу динамической?

Да, чтение параметров строки запроса на этапе выполнения (request-time) означает, что сервер не может статически сгенерировать точный HTML страницы во время сборки приложения (next build). Поэтому использование searchParams переводит рендеринг маршрута в динамический (dynamic rendering).

4. В каких случаях Context API проигрывает Zustand или Redux Toolkit?

React Context не является инструментом оптимизации производительности. При изменении значения в контексте все подписанные компоненты-потребители (consumers) запускают процесс ререндеринга. В ситуациях с частыми обновлениями данных (например, ввод текста, таймеры, перетаскивание элементов) специализированные селекторы в Redux Toolkit или Zustand предотвращают лишние перерисовки интерфейса.

5. Как определить, что в проекте нарушена архитектура слоев состояния?

Главные маркеры нарушения архитектуры:

  • Невозможность скопировать ссылку на открытую вкладку или отфильтрованный список.
  • Данные в разных блоках интерфейса показывают противоречивую информацию об одном и том же объекте бэкенда.
  • Глобальный стор перегружен редьюсерами, содержащими только флаги загрузки isLoading и списки полученных сущностей.
  • При переходе на другую страницу интерфейс «моргает» устаревшими данными из-за несброшенного глобального стейта.

Вывод

Грамотное распределение данных по 4 слоям устраняет хаос в кодовой базе и предотвращает разрастание технического долга. Перед созданием новой переменной состояния задайте себе 4 вопроса:

  1. Нужны ли эти данные вне текущего блока интерфейса? Если нет — используйте Local State.
  2. Должно ли состояние сохраняться в ссылке и истории браузера? Если да — используйте URL State.
  3. Принадлежат ли эти данные серверу и базе данных? Если да — используйте Server State (кэш запросов).
  4. Управляют ли эти данные поведением всего клиентского приложения? Если да — используйте Global State.

Источники

Это авторская статья, основанная на личном опыте и субъективном взгляде автора. Заметили ошибку или битую ссылку? Сообщите нам: info@codesrc.ru - мы оперативно исправим. Спасибо, что помогаете делать блог лучше.
Следите за нами в соцсетях:

Читайте также