От PHP-сессий до TanStack: как мы управляем состоянием в веб разработке

Управление состоянием в вебе — это не просто выбор между PHP-сессиями и React, а осознанное разделение данных по их назначению, источнику истины и способу обновления. Статья объясняет, как ответить на пять ключевых вопросов о каждом значении и подобрать подходящие инструменты — от серверных сессий до TanStack Query, Next. js и Astro — чтобы избежать дублирования и противоречий.

Управление состоянием в вебе — это не просто выбор между PHP-сессиями и React, а осознанное разделение данных по их назначению, источнику истины и способу обновления. Статья объясняет, как ответить на пять ключевых вопросов о каждом значении и подобрать подходящие инструменты — от серверных сессий до TanStack Query, Next. js и Astro — чтобы избежать дублирования и противоречий.

PHP-сессии, React, TanStack Query, Next.js и Astro решают разные части этой задачи. Сессии связывают запросы с пользователем, React управляет интерфейсом, TanStack Query обновляет полученные с сервера данные, а Next.js и Astro определяют, где и как формируется страница.

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

Основные задачи управления состоянием

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

Эти решения связаны, но не заменяют друг друга. Например, выбор браузерного хранилища не определяет, кто вправе менять данные, а серверный вывод страницы не гарантирует, что её содержимое останется актуальным.

Назначение: что описывают данные

  • Данные бизнеса: товары, заказы, документы, остатки.
  • Личность и доступ: кто пользователь и что ему разрешено.
  • Навигацию: открытый экран, поиск, фильтры, сортировку.
  • Вид интерфейса: открытые окна, раскрытые меню, выделенные строки.
  • Незавершённую работу: черновики, формы, этапы отправки и согласования.

Основная версия: кто определяет правильное значение

  • Сервер — например, для цены и статуса заказа.
  • Браузер — например, для открытого меню.
  • Несколько участников по установленным правилам согласования — например, в совместном редакторе.

Основную версию часто называют источником истины. Остальные копии должны обновляться по понятным правилам.

Область использования: кому нужны данные

  • Одному компоненту интерфейса.
  • Группе связанных компонентов.
  • Всему приложению.
  • Нескольким вкладкам браузера.
  • Разным устройствам или пользователям.

Срок жизни: как долго нужны данные

  • До завершения операции или запроса.
  • Пока существует компонент.
  • Пока открыта вкладка или действует сессия.
  • Между посещениями сайта.
  • Постоянно, как запись о заказе.

Место хранения: где находится значение

  • В памяти приложения.
  • В адресе страницы — URL.
  • В браузерном хранилище.
  • В cookie.
  • В серверной сессии или серверном кэше.
  • В базе данных.

Обновление: как распространяются изменения

  • Прямое изменение локального значения.
  • Запрос к серверу и обработка ответа.
  • Пометка кэша как устаревшего и повторная загрузка.
  • Получение событий от сервера.
  • Синхронизация после восстановления связи.

Формирование интерфейса: где создаётся страница

  • На сервере.
  • В браузере.
  • Частично на сервере, частично в браузере.

Эти признаки дополняют друг друга. Например, состав корзины — данные бизнеса, основная версия которых может храниться на сервере. Браузер держит их копию в кэше. При этом открытие панели корзины — отдельное состояние интерфейса.

Такое разделение помогает избежать двух ошибок: хранить всё одинаково и поручать нескольким инструментам управление одним значением без правил согласования.

От серверных страниц к гибридным приложениям

Серверные приложения: как запомнить пользователя между запросами

HTTP сам по себе не связывает независимые запросы с одним пользователем. В классическом приложении сервер получает запрос, читает данные, формирует HTML и отправляет страницу браузеру. Сессия помогает сохранить контекст между обращениями: например, помнить, кто вошёл в систему.

В PHP после запуска или возобновления сессии приложение работает с массивом $_SESSION. Идентификатор сессии связывает запрос с сохранёнными данными. Обычно браузер передаёт его в cookie.[1]

PHP не требует перезагружать страницу при каждом действии. AJAX-запросы и динамические интерфейсы существовали до распространения React. Перезагрузка была распространённым способом работы, а не ограничением языка.

Одностраничные приложения: больше работы в браузере

В одностраничном приложении, или SPA, многие переходы и действия происходят без полной загрузки нового документа. Браузер управляет экранами, формами, результатами запросов и промежуточными состояниями: загрузкой, ошибкой, ожиданием ответа.

Для согласованной работы компонентов появились общие хранилища состояния. Но у открытого окна и каталога товаров разные правила обновления. Окном управляет браузер. Каталогом — сервер, а браузер хранит лишь полученную копию.

Гибридные приложения: разделение работы между сервером и браузером

Современные инструменты позволяют формировать часть интерфейса на сервере, а взаимодействие с пользователем выполнять в браузере. Отдельные библиотеки отвечают за загрузку и обновление серверных данных.

Это не последовательная замена «PHP → React → Next.js». Серверные приложения, SPA и гибридные решения существуют одновременно. Меняется распределение работы, но необходимость управлять состоянием остаётся.

Какие виды состояния есть в приложении

Данные бизнеса

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

В браузере находится копия — серверные данные на клиенте. Она может устареть без действий текущего пользователя: другой сотрудник изменил документ, платёж прошёл в фоне, товар закончился.

Клиентский кэш — временное хранилище полученных данных. Он должен учитывать:

  • какие данные уже загружены;
  • когда они получены;
  • можно ли использовать их без нового запроса;
  • что нужно обновить после изменения.

На этой работе специализируется TanStack Query.[2]

Личность пользователя и права доступа

Здесь важно различать четыре задачи:

  • Аутентификация — подтверждение личности, например проверка пароля.
  • Сессия — сохранение контекста между обращениями.
  • Авторизация — проверка права на действие или объект.
  • Отображение в интерфейсе — имя, фотография и доступность кнопок.

Объект currentUser в браузере не доказывает наличие прав. Скрытая кнопка тоже не защищает данные. Сервер должен проверять доступ при каждом защищённом обращении. Проверка маршрута в браузере помогает пользователю ориентироваться, но не заменяет серверную защиту.[3]

Навигация: какой экран открыт

Адрес страницы удобно использовать для состояния, которое нужно восстановить после перезагрузки или передать другому человеку:

  • выбранного раздела;
  • идентификатора товара или документа;
  • поискового запроса;
  • фильтров и сортировки;
  • номера страницы списка.

Например:


/products?category=books&sort=price&page=2

Такой адрес можно сохранить в закладках или открыть в новой вкладке. История браузера позволяет вернуться к предыдущим параметрам. TanStack Router предоставляет средства чтения и проверки параметров адреса.[4]

При этом ввод в поле поиска и подтверждённый поисковый запрос — разные значения. Первый можно временно хранить в компоненте, а второй — записывать в URL после отправки формы или небольшой задержки.

Вид интерфейса

Открытое диалоговое окно, раскрытое меню, выделенные строки и временные подсказки обычно относятся к состоянию браузера.

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

Некоторые настройки нужно сохранять надолго. Например, выбранную тему можно записать в localStorage или профиль пользователя. Тема остаётся состоянием интерфейса, а хранилище определяет срок её сохранения.

Черновики, формы и этапы работы

Пока пользователь редактирует документ, его ввод ещё не стал подтверждённой серверной версией. Поэтому в форме нужно разделять:

  • исходные данные;
  • текущий черновик;
  • изменённые поля и поля, с которыми пользователь уже взаимодействовал;
  • ошибки проверки;
  • состояние отправки;
  • ответ сервера.

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

Для сложной операции лучше явно перечислить допустимые состояния: «редактирование → проверка → отправка → успех или ошибка». Это надёжнее набора независимых признаков, которые могут противоречить друг другу. React рекомендует избегать противоречивого и избыточного состояния.[5]

Что не нужно хранить отдельно

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

Вместо копии выбранного объекта обычно достаточно хранить его идентификатор selectedId. Сам объект можно найти в основном списке. Если вычисление дорогое, его результат можно запоминать до изменения исходных данных. Этот приём называется мемоизацией; он ускоряет расчёт, но не создаёт новую основную версию данных.[5]

Где хранить состояние

Память приложения

Память компонентов и общих хранилищ подходит для быстрых изменений интерфейса и временного кэша.

Нужно учитывать два ограничения:

  • без дополнительного сохранения данные исчезают при перезагрузке;
  • локальное состояние компонента обычно исчезает после его удаления из дерева интерфейса.

Значение в памяти не становится доступным на другом устройстве и не сохраняется автоматически между посещениями.

Адрес страницы

URL подходит для состояния, которое должно открываться по ссылке. В него нельзя помещать пароли, идентификаторы сессий и другие секреты: адрес попадает в историю, журналы, аналитику и скопированные ссылки.

Перед использованием параметров нужно:

  • проверить типы;
  • ограничить допустимые значения;
  • установить значения по умолчанию;
  • обработать ошибочный ввод.

Например, page=-100 не должен приводить к сбою.

Браузерные хранилища

localStorage: небольшие данные между посещениями

  • Подходит для несекретных настроек и простых черновиков.
  • Хранит строковые значения.
  • Доступен JavaScript-коду того же источника: с одинаковыми протоколом, именем хоста и портом.
  • Обычно сохраняется после закрытия браузера.
  • Выполняет операции синхронно, поэтому большие записи могут задерживать работу интерфейса.

sessionStorage: данные отдельной вкладки

  • Подходит для временных данных, которые должны пережить перезагрузку вкладки.
  • Изолирован по источнику и вкладке.
  • Обычно очищается при закрытии вкладки.
  • Не связан с серверной сессией, несмотря на похожее название.

IndexedDB: большие объёмы и работа без сети

  • Подходит для структурированных данных, локальных копий и очередей отправки.
  • Работает асинхронно: операции не требуют синхронного ожидания в основном потоке JavaScript.
  • Требует продумать структуру данных, её обновление между версиями приложения и синхронизацию с сервером.

MDN описывает различия localStorage и sessionStorage и рекомендует рассматривать IndexedDB для более крупных объёмов данных.[6]

Для любого браузерного хранилища нужно обрабатывать ошибки записи, ограничения объёма, очистку и старые форматы данных. Хранение на устройстве не гарантирует сохранность навсегда.

Cookie

Cookie — небольшой набор данных, который браузер хранит и отправляет серверу с подходящими запросами.

Cookie и сессия — не одно и то же. Cookie может содержать только идентификатор, по которому сервер находит сессию. В другой схеме она может содержать сами данные, защищённые подписью или шифрованием. Подпись защищает от изменения, но сама по себе не скрывает содержимое.

Атрибут HttpOnly запрещает JavaScript читать cookie. Это свойство cookie, а не отдельный механизм сессий и не аналог PHP-сессий в Node.js.

В отличие от cookie, содержимое localStorage и sessionStorage не добавляется к HTTP-запросам автоматически.[7]

Серверная сессия, кэш и база данных

У этих хранилищ разные задачи:

  • Сессия сохраняет контекст взаимодействия пользователя с приложением.
  • Серверный кэш ускоряет получение данных и вычисления. Его содержимое обычно можно восстановить.
  • База данных хранит основные данные бизнеса и обеспечивает необходимые правила целостности.

Заказ не должен существовать только в сессии. Цена для оплаты не должна зависеть от суммы, которую прислал браузер: сервер повторно рассчитывает её по своим правилам.

Как работают PHP-сессии

Обработка запроса

  1. Браузер отправляет запрос и cookie с идентификатором сессии, если она есть.
  2. PHP запускает или возобновляет сессию через session_start().
  3. Приложение читает и изменяет $_SESSION.
  4. Обработчик сохраняет данные для следующих обращений.

Способ хранения настраивается. Сессия не обязана храниться в локальном файле: PHP позволяет подключать собственные обработчики.[1][8]

Какие данные получает браузер

JavaScript не может напрямую прочитать серверный $_SESSION. Сервер может включить разрешённые поля в HTML или вернуть их через программный интерфейс — API, например по адресу /api/me.

Передавать всю сессию нельзя. Для интерфейса достаточно нужных ему полей: например, имени и фотографии. Внутренние токены и служебные данные должны оставаться на сервере.

Что меняется при росте нагрузки

Если запросы обслуживают несколько серверов, каждый из них должен находить нужную сессию. Для этого используют общее хранилище или другой заранее выбранный способ распределения запросов.

Также нужно проверить:

  • срок действия и удаление истёкших записей;
  • принудительное завершение сессии;
  • поведение при недоступности хранилища;
  • одновременные запросы одного пользователя;
  • влияние блокировок на скорость.

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

PHP может отдавать HTML, предоставлять API или работать с React. При этом не все PHP-приложения и системы управления сайтами используют именно $_SESSION: у конкретной системы может быть собственный механизм входа.

Инструменты для клиентского состояния

Встроенные средства React

  • useState — хранит отдельные значения внутри компонента.
  • useReducer — объединяет правила изменения связанных значений. Функция-обработчик получает текущее состояние и действие, а возвращает новое состояние.
  • Context — передаёт значение по группе вложенных компонентов без ручной передачи через каждый промежуточный компонент.
  • Поднятие состояния — переносит общее значение к родителю, который управляет несколькими компонентами.

Context не сохраняет данные между посещениями и не управляет кэшем запросов. Он передаёт значение; правила обновления задают другие средства.

React рекомендует объединять связанные значения, не дублировать данные и избегать лишней вложенности.[5]

Базовый подход: useContext и TanStack Query

useContext: начать со встроенных средств React

Для общего состояния интерфейса сначала стоит рассмотреть useState или useReducer вместе с Context. Компонент-провайдер хранит значение и правила его изменения, а вложенные компоненты получают их через useContext.

Например, так можно передавать выбранную тему, настройки отображения или состояние панели:

  • useState хранит простое значение, а useReducer описывает более сложные изменения.
  • Провайдер делает значение доступным нужной части приложения.
  • useContext позволяет компонентам прочитать значение и получить функции обновления.

Если данные нужны одному компоненту, Context не требуется. Для нескольких компонентов провайдер лучше размещать у их ближайшего общего родителя, а не делать всё состояние глобальным.

При изменении значения контекста React обновляет компоненты, которые его читают. Поэтому независимые значения с разной частотой обновления полезно разделять по контекстам. Сам useContext не загружает данные, не проверяет их свежесть и не сохраняет их между посещениями.

TanStack Query: следующий шаг для данных с сервера

Простой запрос можно выполнить через fetch, а результат хранить в состоянии React. Но по мере роста приложения приходится вручную добавлять обработку загрузки и ошибок, кэш, повторы и обновление данных после записи.

TanStack Query берёт эту работу на себя:

  • хранит результаты запросов в общем кэше;
  • сообщает о загрузке, ошибке и фоновом обновлении;
  • управляет свежестью и повторным получением данных;
  • помогает обновлять кэш после изменений на сервере.[1]

Это улучшение работы с серверными данными, а не замена useContext. Инструменты дополняют друг друга: Context передаёт общие настройки интерфейса, TanStack Query управляет загруженными товарами, документами и другими серверными данными. Копировать результаты запросов в собственный Context обычно не нужно.

Практическая отправная точка — локальное состояние React, useContext для общих значений и TanStack Query для сложной работы с серверными данными. Подключать все эти механизмы в каждом проекте не требуется.

Дополнительные инструменты: по необходимости

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

Zustand

  • Подходит для общих клиентских данных и сравнительно простой логики обновления.
  • Позволяет вынести состояние за пределы отдельных компонентов.
  • Не определяет автоматически, когда серверные данные устарели и как разрешать конфликты изменений.

Redux Toolkit

  • Подходит, когда нужны явные действия и единые правила изменения состояния.
  • Помогает организовать сложную логику, над которой работает команда.
  • Не заменяет серверную проверку доступа и выбор основной версии данных.

Jotai и другие атомарные модели

  • Разбивают состояние на небольшие независимые значения — атомы.
  • Позволяют строить зависимости между этими значениями.
  • Не задают сами по себе правила обмена данными с сервером.

MobX

  • Использует наблюдаемые объекты: интерфейс реагирует на изменения их значений.
  • Поддерживает значения, вычисляемые из других данных.
  • Не определяет автоматически правила хранения и обновления ответов API.

XState и машины состояний

  • Описывают допустимые состояния процесса и переходы между ними.
  • Полезны для многошаговых операций, отмены, повторов и ожидания внешних событий.
  • Не заменяют базу данных и проверку прав.

Библиотеки форм

  • Управляют полями, проверками и сообщениями об ошибках.
  • Отслеживают, что изменено и с какими полями пользователь взаимодействовал.
  • Помогают организовать отправку, но не отменяют серверную проверку данных и обработку конфликтов.

Это ориентиры выбора, а не рейтинг. Небольшому интерфейсу может хватить встроенных средств React. В большом приложении несколько инструментов допустимы, если они не дублируют работу друг друга.

Утверждение «серверные данные нельзя хранить в Redux» слишком категорично. RTK Query входит в Redux Toolkit и управляет запросами и кэшем. Проблема возникает не из-за названия хранилища, а из-за ручного создания второй системы обновления тех же данных.[9]

TanStack Query: загрузка и обновление серверных данных

Основная задача

TanStack Query получает удалённые данные, хранит их в кэше и помогает обновлять. Он не заменяет сервер, базу данных, проверку доступа или всё состояние интерфейса.[2]

Для запроса задают:

  • Ключ — обозначает, какие данные находятся в кэше.
  • Функцию загрузки — возвращает результат асинхронной операции.
  • Настройки — например, время, в течение которого данные считаются свежими.

Библиотека сообщает о загрузке, ошибке и полученном результате.[10]


const productsQuery = useQuery({

  queryKey: ['products', { category, page, sort }],

  queryFn: async ({ signal }) => {

    const params = new URLSearchParams({

      category,

      page: String(page),

      sort,

    });

    const response = await fetch(`/api/products?${params}`, { signal });

    if (!response.ok) throw new Error('Не удалось загрузить товары');

    return response.json();

  },

  staleTime: 60_000,

});

Пример предполагает, что TanStack Query установлен, а компоненты обёрнуты в QueryClientProvider. Код показывает загрузку товаров, но не настройку входа и прав доступа.

Все параметры, меняющие результат, должны входить в ключ. Для персональных данных нужно учитывать пользователя, а для изолированных рабочих пространств — организацию. При смене учётной записи соответствующий кэш очищают или создают заново. Сам ключ не защищает данные: доступ проверяет сервер.

Свежесть и срок хранения — разные настройки

  • staleTime — время, в течение которого данные считаются свежими.
  • gcTime — время хранения неактивной записи кэша до удаления.

По умолчанию данные считаются устаревшими сразу. Устаревший запрос может повториться, когда его начинает использовать новый компонент, пользователь возвращается к окну или восстанавливается сеть. Неактивные записи на клиенте по умолчанию обычно удаляются через пять минут.[11]

Истечение staleTime не означает немедленную отправку запроса. Оно лишь разрешает считать данные устаревшими. Для регулярного опроса сервера есть отдельная настройка.

Запись на сервер и обновление кэша

Операцию изменения серверных данных в TanStack Query называют мутацией. Это, например, сохранение формы, добавление товара или удаление записи.

После изменения можно:

  1. Обновить кэш подтверждёнными данными из ответа.
  2. Пометить связанные записи как устаревшие, чтобы используемые данные загрузились заново. Это называется инвалидацией кэша.
  3. Сначала показать ожидаемый результат, а после ответа подтвердить его или отменить.

Последний подход называют оптимистическим обновлением. Он ускоряет ощущаемую реакцию интерфейса, но не подтверждает успех операции. Для оплаты и других критических действий нужно явно показывать ожидание ответа сервера.

Ошибки, отмена и большие списки

При загрузке данных нужно предусмотреть:

  • отдельное отображение первой загрузки и фонового обновления;
  • сохранение полезных старых данных при временной ошибке;
  • отмену ненужных запросов;
  • защиту от ситуации, когда старый ответ приходит позже нового;
  • загрузку списка по страницам или частям;
  • разные действия при временном сбое, неверном вводе и отказе доступа;
  • ограниченные повторы с задержкой.

Повтор записи может создать дубль. Для важных операций серверу нужна идемпотентность: повтор одного и того же запроса не должен повторять бизнес-действие, например создавать второй заказ.

Альтернативы

Для похожих задач используют RTK Query, SWR, GraphQL-клиенты и средства самого фреймворка. У некоторых GraphQL-клиентов кэш хранит объекты отдельно по идентификаторам и связывает ответы с этими объектами. У кэша запросов единицей хранения служит результат запроса. Из-за этого правила обновления различаются.

Если данные нужны только для серверного вывода страницы, отдельная клиентская библиотека может быть лишней. Она полезнее, когда нужны общий кэш между экранами, фоновые обновления, загрузка списков частями и частые изменения.

Где формируется страница: React, Next.js и Astro

Рендеринг — процесс формирования интерфейса из данных. Он может происходить на сервере, в браузере или в обоих местах.

Основные способы рендеринга

CSR: интерфейс формируется в браузере

  • Браузер загружает JavaScript и строит основную часть интерфейса.
  • Приложению нужно получить необходимые данные и установить начальное состояние.
  • Скорость появления полезного содержимого зависит в том числе от загрузки и выполнения скриптов.

SSR: HTML формируется на сервере при запросе

  • Сервер получает данные и отправляет готовый HTML.
  • Персональные данные разных запросов нужно изолировать.
  • Для работы интерактивных компонентов браузер подключает обработчики к готовой разметке. Этот процесс называется гидратацией.

SSG: HTML формируется заранее

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

Гибридный подход

  • Разные страницы или части интерфейса используют разные способы формирования.
  • Серверные и клиентские данные нужно согласовать.
  • Для каждого кэша требуется определить условия обновления.

Потоковая передача позволяет отправлять результат частями по мере готовности. Это способ доставки, а не отдельное хранилище состояния.

SPA описывает переходы между экранами без полной загрузки документа. Это не синоним CSR: первая страница одностраничного приложения тоже может быть подготовлена сервером.

React

React — библиотека для построения интерфейса из компонентов. Она предоставляет средства локального состояния и связи между компонентами.

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

Next.js и серверные компоненты

В App Router страницы и общие макеты по умолчанию являются серверными компонентами. Они могут получать данные и передавать разрешённые значения клиентским компонентам. Клиентские компоненты нужны для обработчиков событий, локального состояния и браузерных API.[12]

Важно различать:

  • Серверный компонент и компонент без данных. Серверный компонент может читать и преобразовывать данные, но не использует клиентские интерактивные хуки вроде useState.
  • Клиентский компонент и рендеринг только в браузере. Директива 'use client' не запрещает предварительный вывод HTML на сервере при первой загрузке.
  • RSC и SSR. React Server Components определяют выполнение компонентов и передачу их результата. SSR означает формирование HTML на сервере.
  • Серверное выполнение и секретность результата. Всё переданное браузеру, в том числе свойства компонентов, становится доступно клиенту.[12]

Не нужно повторять каждый серверный запрос через TanStack Query. Сначала стоит определить, понадобятся ли данным фоновые обновления и другие клиентские возможности после первого вывода.

Astro и острова интерактивности

Astro позволяет отдавать основную страницу как HTML, а JavaScript подключать к отдельным интерактивным участкам — островам.

Объём скриптов зависит от приложения. Обещание «Astro всегда отправляет 0 КБ JavaScript» неверно, если на странице есть скрипты и интерактивные компоненты.

Отдельные React-острова не образуют общего дерева, поэтому Context не связывает их автоматически. Внутри одного React-дерева он работает. Для обмена состоянием между островами Astro рекомендует Nano Stores и описывает альтернативы, включая браузерные события.[13]

Острова меняют объём и границы интерактивности, но не отменяют сессии, API и серверную проверку данных.

Что входит в TanStack

TanStack — название семейства инструментов:

  • TanStack Query управляет удалёнными данными и их кэшем.
  • TanStack Router отвечает за маршрутизацию и состояние в URL.
  • TanStack Start предоставляет средства разработки и клиентской, и серверной части приложения.

Поэтому при выборе технологии важно называть конкретный пакет.[2][4][14]

Как согласовать несколько копий данных

Назначить основную версию

У одного значения может быть несколько копий, но должно быть понятно, какая версия считается подтверждённой.

Для корзины это может выглядеть так:

  • сервер хранит подтверждённый состав;
  • кэш запросов хранит полученную копию;
  • форма хранит ещё не отправленные изменения;
  • состояние интерфейса определяет, открыта ли панель;
  • сервер заново рассчитывает сумму перед оплатой.

Копировать ответ TanStack Query в Zustand только ради удобства обычно не нужно. Для редактирования можно создать черновик, но важно определить, когда он создаётся, сохраняется и удаляется.

Учитывать каждый кэш отдельно

В приложении могут одновременно работать:

  • кэш HTTP-ответов в браузере или сети доставки содержимого — CDN;
  • серверный кэш фреймворка;
  • кэш переходов между страницами;
  • клиентский кэш TanStack Query.

Обновление одного не гарантирует обновления остальных. После записи нужно определить, какие страницы, списки и карточки должны измениться. Персональные ответы нельзя помещать в общий кэш без надёжного разделения доступа.

Согласовать серверный вывод и гидратацию

Сервер может заранее получить данные и передать их в клиентский кэш. Для этого значения преобразуют в формат передачи — сериализуют.

Нужно выполнить три условия:

  • начальные данные сервера и браузера согласованы;
  • передаются только разрешённые поля;
  • сериализация безопасна и не позволяет внедрить исполняемый код.

Общий серверный QueryClient с персональными данными может привести к утечке между пользователями. Документация TanStack Query требует изолировать кэш по запросам. Настройка свежести также помогает избежать лишней загрузки сразу после гидратации.[15]

Обработать одновременное редактирование

Если два человека меняют документ, клиентское хранилище само по себе не предотвращает потерю изменений.

Для обычного приложения можно:

  1. Передавать номер редактируемой версии.
  2. Проверять его на сервере перед сохранением.
  3. Отклонять запись, если версия устарела.
  4. Предлагать перечитать документ или объединить изменения.

Для редактора с одновременной работой могут потребоваться специальные алгоритмы. OT преобразует операции с учётом чужих изменений, а CRDT задаёт структуры и правила объединения, позволяющие согласовать копии. Это отдельная задача, которую нельзя решить настройкой свежести кэша.

Получать изменения без ручного обновления

WebSocket и SSE позволяют серверу отправлять события браузеру. Приложение должно решить, что делать с событием:

  • изменить запись в кэше;
  • пометить данные как устаревшие;
  • запросить актуальную версию.

После восстановления соединения нужно получить пропущенные события или заново загрузить состояние. Само наличие постоянного соединения не гарантирует согласованность данных.

Поддержать работу без сети

Сохранённого кэша недостаточно для полноценного офлайн-режима. Нужны:

  • локальная копия необходимых данных;
  • очередь неотправленных операций;
  • защита от повторного выполнения;
  • правила повторов;
  • разрешение конфликтов после подключения.

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

Согласовать вкладки и устройства

Хранилище в памяти одной вкладки не обновляет другие вкладки автоматически. Для уведомлений можно использовать событие storage или канал BroadcastChannel. Событие storage сообщает другим подходящим документам об изменениях Web Storage.[6]

Для разных устройств нужен обмен через сервер: события, повторные запросы или другой предусмотренный способ синхронизации.

Безопасность сессии

Не хранить пароль в состоянии приложения

Пароль не нужно сохранять в клиентском хранилище, localStorage или $_SESSION. После проверки приложению обычно достаточно идентификатора пользователя и контекста входа.

JWT — формат токена, а cookie — способ хранения и передачи данных. JWT можно передавать через cookie. Сам выбор JWT не решает вопросы безопасного хранения, срока действия, завершения доступа и изменения прав.

Защитить cookie и срок действия сессии

Базовые меры:

  1. Использовать HTTPS на всём протяжении сессии.
  2. Настроить Secure, чтобы cookie передавалась по защищённому соединению.
  3. Настроить HttpOnly, чтобы JavaScript не мог прочитать cookie.
  4. Выбрать подходящий SameSite, ограничивающий передачу cookie при межсайтовых запросах.
  5. Ограничить область действия cookie; при подходящей схеме использовать префикс __Host-.
  6. Менять идентификатор сессии после входа и изменения уровня прав.
  7. Ограничить на сервере время бездействия и общий срок действия.
  8. При выходе завершать серверную сессию.
  9. Не передавать идентификаторы сессий через URL и не хранить токены доступа в Web Storage.[7]

HttpOnly не устраняет последствия XSS — внедрения вредоносного кода на страницу. Такой код может выполнять запросы от имени пользователя, даже не читая cookie.

SameSite помогает против CSRF — отправки нежелательного запроса через браузер вошедшего пользователя. Но он не заменяет все остальные проверки: для изменяющих запросов могут понадобиться проверка происхождения и CSRF-токен.[7]

Разделить доступ к ответу и права пользователя

Когда интерфейс и API находятся на разных источниках, нужно отдельно настроить передачу cookie и других данных подтверждения входа.

CORS определяет, может ли браузерный JavaScript прочитать ответ другого источника. Он не проверяет права пользователя и не заменяет серверную авторизацию.

Различия между источниками и сайтами тоже важны: у источника учитываются протокол, имя хоста и порт, а правила межсайтовой передачи cookie используют другое понятие границы. Поэтому настройки CORS и SameSite нельзя считать взаимозаменяемыми.

Оставить внешние токены на сервере, если браузеру они не нужны

В схеме Backend for Frontend, или BFF, отдельная серверная часть обслуживает интерфейс и обращается к внешним API. Токены остаются на сервере, а браузер использует защищённую сессию.

OWASP рекомендует защищённые HttpOnly-cookie или BFF вместо хранения учётных данных и токенов в localStorage и sessionStorage.[7]

Очистить данные после выхода и смены организации

После выхода нужно:

  • завершить серверную сессию;
  • отменить незавершённые персональные запросы или запретить применение их ответов;
  • очистить пользовательский кэш;
  • удалить конфиденциальные черновики и локальные копии по правилам приложения.

Иначе поздний ответ может снова показать данные уже вышедшего пользователя.

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

Пример: интернет-магазин на PHP и React

Где хранить каждую часть состояния

Вход пользователя

  • Сервер определяет, действительна ли сессия.
  • Браузер получает только необходимые сведения, например через /api/me.
  • Истечение сессии обрабатывается по ответам сервера.

Каталог и остатки

  • Основные данные находятся в базе данных и проверяются сервером.
  • TanStack Query хранит полученную копию.
  • После изменений данные обновляются запросом или по событию.

Категория, сортировка и страница списка

  • Подтверждённые параметры находятся в URL.
  • Средство маршрутизации читает и проверяет их.
  • История браузера сохраняет переходы между вариантами списка.

Открытие панели корзины

  • Хранится в локальном состоянии или общем хранилище интерфейса.
  • Меняется сразу по действию пользователя.
  • Не требует обращения к серверу.

Состав корзины

  • В выбранной схеме подтверждённая версия хранится на сервере.
  • Браузер использует кэш запросов.
  • После изменения применяет ответ сервера или повторно запрашивает корзину.

Адрес доставки до отправки

  • Хранится как черновик формы.
  • Проверяется при вводе и перед отправкой.
  • После отправки повторно проверяется сервером.

Заказ и итоговая цена

  • Подтверждаются сервером и записываются в базу данных.
  • Сервер заново проверяет цену и остатки.
  • Изменения выполняются согласованно, а повтор запроса не создаёт второй заказ.

Как проходит покупка

  1. Сервер проверяет вход и устанавливает защищённую cookie сессии. Отдельный токен для JavaScript не обязателен.
  2. Браузер получает разрешённые сведения о пользователе и каталог.
  3. Фильтры из URL входят в ключ запроса товаров.
  4. При добавлении товара браузер отправляет запрос изменения. Сервер проверяет доступ, количество и действующие правила.
  5. Корзина обновляется по ответу или загружается заново. Если интерфейс заранее показал ожидаемый результат, при отказе он отменяет это изменение.
  6. При оформлении сервер повторно рассчитывает сумму и защищает операцию от дублей.
  7. При выходе сессия завершается, а персональные данные удаляются из клиентского состояния.

Вместо PHP можно использовать Node.js, Python, Java или другой серверный язык. Распределение задач остаётся тем же.

Как выбрать архитектуру под задачу

Контентный сайт, документация или посадочная страница

  • Начните со статического или серверного HTML.
  • При необходимости используйте Astro и небольшие интерактивные блоки.
  • Добавляйте клиентское состояние по мере появления персонализации и сложных взаимодействий.

Небольшое серверное приложение

  • Используйте серверные страницы, сессии и обычные формы.
  • Добавляйте JavaScript для конкретных действий без перезагрузки.
  • Переходите к отдельному клиентскому приложению, если сложность интерфейса это оправдывает.

Внутренняя панель управления

  • Начните с React, маршрутизации и локального состояния.
  • Для повторно используемых серверных данных добавьте кэш запросов.
  • Для общих настроек и взаимодействий интерфейса при необходимости подключите общее хранилище.

Публичный сервис с динамическими данными

  • Рассмотрите гибридный фреймворк, например Next.js.
  • Получайте начальные данные на сервере там, где это полезно для первого отображения.
  • Добавляйте клиентский кэш для дальнейших интерактивных обновлений.

Приложение, уже использующее Redux Toolkit

  • Сохраните понятную существующую схему.
  • Рассмотрите RTK Query для удалённых данных.
  • Не подключайте второй менеджер запросов без отдельной причины.

Совместная работа или работа без сети

  • Сначала определите модель версий и правила согласования.
  • Спроектируйте локальное хранение и очередь операций.
  • Добавьте получение событий и разрешение конфликтов в соответствии с требованиями.

Это отправные точки, а не ограничения инструментов. Next.js подходит не только для сервисов по подписке, Astro — не только для блогов, а React можно использовать с серверным рендерингом.

Выбор зависит от сложности взаимодействий, требований к первому отображению, доступности содержимого, инфраструктуры и опыта команды.

Порядок принятия решений

  1. Определить основные данные и правила доступа.
  2. Описать изменения, сроки хранения и восстановление после сбоев.
  3. Выбрать способ формирования страниц и навигации.
  4. Организовать чтение и запись серверных данных.
  5. Добавить общее клиентское хранилище там, где локального состояния недостаточно.
  6. Проектировать офлайн-режим и постоянную доставку событий только при наличии таких требований.

Ошибки, тестирование и контроль работы

Типичные ошибки

Делать всё состояние общим

  • Проблема: появляются скрытые зависимости, а временные данные живут слишком долго.
  • Решение: хранить значение рядом с теми компонентами, которым оно нужно. Расширять область использования по необходимости.

Дублировать серверные данные в нескольких хранилищах

  • Проблема: копия в TanStack Query расходится с копией в хранилище интерфейса.
  • Решение: использовать один кэш. Отдельную копию создавать только как черновик с понятными правилами сохранения.

Держать фильтры и в URL, и в памяти без правил

  • Проблема: ссылка, история браузера и экран показывают разные значения.
  • Решение: считать URL основной версией подтверждённых параметров, а незавершённый ввод хранить отдельно.

Доверять присланной цене или роли

  • Проблема: пользователь может изменить запрос вручную.
  • Решение: проверять права и рассчитывать итоговые значения на сервере.

Использовать общий персональный кэш при SSR

  • Проблема: данные одного пользователя могут попасть в ответ другому.
  • Решение: изолировать экземпляры по запросам и передавать только разрешённые поля.

Повторять запросы без ограничений

  • Проблема: возникают дубли действий и лишняя нагрузка.
  • Решение: различать ошибки, ограничивать повторы и защищать важные операции от повторного выполнения.

Сохранять кэш бессрочно

  • Проблема: приложение показывает устаревшие данные или данные предыдущей учётной записи.
  • Решение: задавать сроки хранения, учитывать версию формата и очищать данные при смене пользователя.

Показывать ожидаемый результат без отмены при ошибке

  • Проблема: интерфейс сообщает об изменении, которого сервер не подтвердил.
  • Решение: согласовать результат с ответом, при отказе отменить предварительное изменение и объяснить причину.

Выполнять выход только в интерфейсе

  • Проблема: серверная сессия остаётся действительной.
  • Решение: завершить её на сервере и очистить связанные данные в браузере.

Что тестировать

  • Допустимые изменения состояния, включая ошибку и отмену.
  • Проверку параметров URL и открытие по прямой ссылке.
  • Первую загрузку, пустой результат и фоновое обновление.
  • Временную потерю сети и поздние ответы.
  • Обновление списков и карточек после записи.
  • Конфликт версий, двойное нажатие и повтор отправки.
  • Истечение сессии и выход во время запроса.
  • Смену пользователя и организации.
  • Изоляцию серверного кэша и отсутствие секретов в переданных данных.
  • Перезагрузку, несколько вкладок и восстановление после работы без сети, если эти сценарии поддерживаются.

За чем следить после запуска

  • Временем выполнения запросов.
  • Количеством повторов и дублей.
  • Частотой ошибок и конфликтов.
  • Размером кэша.
  • Временем до появления полезного содержимого и готовности интерфейса к работе.
  • Лишними повторными обновлениями компонентов.

Для сессий полезно регистрировать создание, продление, завершение и истечение срока. В журналы нельзя записывать секреты и полные идентификаторы сессий. OWASP рекомендует отслеживать жизненный цикл сессий, не раскрывая их содержимое.[7]

Дополнительные материалы

Состояние и формирование интерфейса

Запросы, кэш и навигация

Хранение и безопасность

Ссылки ведут на документацию преимущественно на английском языке. Названия переведены для удобства чтения статьи.

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

Выводы

Управление состоянием начинается не с библиотеки, а с правил работы с данными.

  • Сессия связывает запросы с пользователем. Действительность входа и права определяет сервер.
  • Данные бизнеса описывают товары, заказы и другие сущности. Их подтверждённая версия не становится клиентской только потому, что отображается в браузере.
  • Кэш запросов хранит временную копию серверных данных и помогает её обновлять.
  • Состояние интерфейса определяет, что открыто, выделено и показано.
  • Черновик хранит ещё не подтверждённые изменения пользователя.
  • URL позволяет восстановить экран и передать его состояние ссылкой.
  • Хранилище определяет, где и как долго лежат данные, но не делает их достоверными.
  • Рендеринг определяет место формирования интерфейса, но не отменяет проверку доступа и согласование копий.

PHP-сессии, React, TanStack Query, Next.js и Astro не заменяют друг друга. Они могут работать вместе, если задачи разделены. Для каждого значения должны быть понятны основная версия, допустимые изменения, срок хранения и действия при сбое.

Практические рекомендации

  1. Составьте карту состояния. Запишите назначение каждого значимого значения, основную версию, место хранения, срок жизни и способ обновления.
  2. Разделяйте решения. Выбирайте механизм сессии, работу с серверными данными, состояние интерфейса и рендеринг отдельно.
  3. Не храните то, что легко вычислить. Суммы, выбранные объекты и отфильтрованные списки часто не требуют отдельных копий.
  4. Начинайте с локального состояния. Общее хранилище добавляйте, когда нескольким частям приложения действительно нужны общие данные.
  5. Используйте URL для состояния, которое нужно передавать ссылкой. Проверяйте параметры и не помещайте туда секреты.
  6. Опишите весь путь серверных данных. Учитывайте загрузку, свежесть, ошибку, повтор, запись, обновление кэша и большие списки.
  7. Отделяйте черновик от подтверждённой версии. Фоновое обновление не должно стирать ввод пользователя.
  8. Обновляйте каждый затронутый кэш. Изменение TanStack Query не означает автоматического обновления серверной страницы или CDN.
  9. Проверяйте доступ и бизнес-правила на сервере. Не доверяйте цене, роли и другим важным значениям из браузера.
  10. Продумайте выход и смену учётной записи. Завершайте сессию, очищайте персональные копии и не применяйте поздние ответы старых запросов.
  11. Проверяйте не только успех. Медленная сеть, конфликт версий и двойная отправка должны быть предусмотренными сценариями.
  12. Добавляйте сложность по необходимости. Небольшому проекту могут хватить серверных форм и useState. Кэш запросов, машины состояний, постоянную доставку событий и офлайн-синхронизацию подключайте под конкретные требования.

Источники и материалы

Ответить

Ваш адрес email не будет опубликован. Обязательные поля помечены *