Vue

Pinia: паттерны и SSR

Корзина интернет-магазина знает про товары, а товары знают про активные скидки. Если каждый стор тянет данные сам, скидка дублируется в трёх местах и рассинхронизируется при первом же изменении. Pinia 3 даёт другой путь: один стор использует другой как обычную функцию, состояние едет с сервера на клиент без потерь, а деструктуризация перестаёт ломать реактивность. Эти четыре приёма отделяют учебный счётчик от стора, который держит реальный чекаут.

  • Чекаут маркетплейса: cartStore читает priceStore, чтобы пересчитать итог с учётом промокода в одном месте
  • Nuxt-приложение: состояние пользователя сериализуется на сервере и гидратируется на клиенте без повторного запроса
  • Дашборд аналитики: плагин Pinia логирует каждую мутацию стора в Sentry для воспроизведения багов
  • GitLab-подобный интерфейс: storeToRefs позволяет вынуть десяток полей из стора в setup без потери реактивности
  • Корпоративное SPA: общий authStore переиспользуется в notificationsStore и billingStore без дублирования токена

Предварительные знания

  • Базовое использование Pinia: defineStore, state, getters, actions из предыдущего урока
  • Реактивность Vue: ref, reactive, computed и что деструктуризация reactive теряет связь
  • Понимание идеи серверного рендеринга на уровне 'HTML собирается на сервере'

Композиция сторов друг из друга

Стор в Pinia это функция. Поэтому внутри одного стора можно вызвать другую store-функцию и работать с её состоянием как с локальной зависимостью. Это убирает дублирование: если итог корзины зависит от цен, цена живёт в одном priceStore, а cartStore просто его читает.

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

Внутри setup-стора useOtherStore() вызывают в теле функции, а не на верхнем уровне модуля. Pinia должна быть уже активна, иначе обращение к стору до app.use(pinia) выбросит ошибку об отсутствующей активной Pinia.

Как один стор Pinia получает доступ к состоянию другого без дублирования данных?

storeToRefs и реактивность при деструктуризации

Инстанс стора это реактивный объект. Прямая деструктуризация const { total } = useCartStore() вынимает обычное значение и рвёт связь: при изменении стора локальная переменная не обновится. storeToRefs оборачивает state и getters в refs, сохраняя реактивность.

Что извлекаютСпособПочему
state и gettersstoreToRefs(store)Нужна реактивность, иначе значение замораживается
actions (функции)Прямая деструктуризация storeФункция не теряет связь при копировании ссылки
Весь сторИспользование store.xxx напрямуюСамый безопасный вариант, ничего не рвётся

Если деструктуризация привела к тому, что значение в шаблоне 'застыло' и не реагирует на изменения стора, первое что проверяют это обёрнут ли источник в storeToRefs.

Почему прямая деструктуризация const { total } = useCartStore() ломает обновление в шаблоне?

Плагины Pinia

Плагин Pinia это функция, которую регистрируют через pinia.use(). Она получает контекст каждого стора и может добавить общее поле, обернуть actions или подписаться на мутации. Так одну сквозную возможность подключают сразу ко всем сторам: персист в localStorage, логирование, метрики.

Контекст плагина даёт store, app, pinia и options. Через `store.$subscribe` подписываются на изменения состояния, через `store.$onAction` на вызовы actions. Возвращённый из плагина объект мержится в каждый стор, поэтому туда добавляют общие поля вроде `$createdAt`.

Official-плагин pinia-plugin-persistedstate делает то же самое production-grade: выбор хранилища, сериализаторы, выбор отдельных путей состояния. Свой плагин пишут, когда нужна нестандартная логика, которой нет в готовом.

Что делает функция, переданная в pinia.use()?

Гидратация состояния при SSR

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

При SSR createPinia() вызывают для каждого запроса отдельно. Один общий инстанс на сервере смешает состояние разных пользователей: данные одного утекут другому. В Nuxt этот жизненный цикл встроен и отдельно настраивать его не нужно.

Зачем при SSR серверное состояние Pinia сериализуют в HTML и читают на клиенте?

Связь с другими темами

Урок углубляет Pinia до продакшен-паттернов. Дальше курс разводит ответственность состояния и применяет гидратацию:

  • Архитектура состояния — Когда стор оправдан, а когда хватит локального ref или provide/inject
  • SSR и Nuxt 4 — Гидратация Pinia это конкретный кейс переноса состояния с сервера на клиент

Итог

  • Composing stores: внутри одного стора можно вызвать useOtherStore() и читать его состояние, избегая дублирования данных
  • storeToRefs извлекает state и getters как refs, сохраняя реактивность при деструктуризации; actions берутся напрямую
  • Плагины Pinia через pinia.use() расширяют каждый стор: добавляют поля, оборачивают actions, подписываются на мутации
  • При SSR состояние сериализуется на сервере (pinia.state.value) и гидратируется на клиенте до монтирования приложения
  • Композицию сторов держат ацикличной: взаимные ссылки A на B и B на A создают циклы инициализации

Связанные уроки

  • vue-29-state-architecture — Освоив паттерны Pinia, разработчик решает где состоянию вообще место: в компоненте, в Pinia или в provide
  • vue-36-ssr-nuxt — Гидратация стора напрямую применяется в Nuxt, где Pinia переносит состояние с сервера на клиент
Pinia: паттерны и SSR

0

1

Войти