Vue

Pinia: управление состоянием

Корзина магазина нужна сразу в шапке (счётчик товаров), на странице товара (кнопка добавить) и на экране оформления (полный список). Передавать её через props сквозь десяток уровней компонентов - боль и хрупкость. Можно завести общий reactive-объект в модуле, но тогда любой компонент сможет молча мутировать его как угодно, и отследить, кто и когда изменил корзину, станет невозможно. Pinia даёт общее хранилище с понятной структурой: данные в state, производные значения в getters, изменения только через actions, и всё это с поддержкой devtools и типов.

  • Корзина магазина, доступная из шапки, карточки товара и экрана оформления одновременно
  • Состояние авторизации: текущий пользователь и его права, нужные множеству экранов и навигационным гвардам
  • Глобальные настройки UI: тема, язык, открытые модалки, общие для всего приложения
  • Кэш загруженных данных, чтобы не запрашивать одно и то же при возврате на экран
  • Уведомления и тосты, которые любой компонент может добавить в общую очередь

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

  • Композаблы: функция, возвращающая реактивное состояние и логику
  • ref, reactive, computed и их поведение
  • storeToRefs и идея сохранения реактивности при деструктуризации

Зачем Pinia вместо общего reactive

Простейший общий стейт - reactive-объект в модуле, импортируемый где нужно. Технически это работает: реактивность общая. Но у подхода нет дисциплины. Любой компонент мутирует объект напрямую, и когда корзина внезапно очистилась, найти виновника тяжело - менять её мог кто угодно. Нет единой точки изменений, нет истории, нет интеграции с инструментами отладки.

Pinia добавляет структуру поверх той же реактивности. Изменения проходят через actions - именованные методы, которые видны в devtools как события. Состояние централизовано в сторах, у каждого свой id. Появляются time-travel отладка, отслеживание мутаций, горячая замена модулей и строгая типизация. Реактивность та же, но с правилами и инструментами вокруг неё.

АспектОбщий reactivePinia
Точка измененийЛюбой компонент напрямуюТолько actions, видны в devtools
Отладкаconsole.log вручнуюDevtools, time-travel, лог мутаций
ТипизацияРучная, легко рассинхронитьВыводится из стора автоматически
Жизненный циклЖивёт вечно в модулеeffectScope, HMR, $dispose

В чём главное преимущество Pinia над обычным общим reactive-объектом?

Setup-стор: defineStore с функцией

Pinia 3 предлагает два стиля стора, и setup-стор - наиболее близкий к Composition API. defineStore принимает id и setup-функцию, которая выглядит как обычный композабл: ref становится state, computed становится getter, обычная функция становится action. То, что функция вернёт, образует публичный интерфейс стора.

Соответствие прямое: ref - это реактивные данные стора, computed - производные значения, кэшируемые и пересчитываемые автоматически, функции - единственный санкционированный способ менять состояние. Перед подключением сторов приложение получает Pinia через app.use(createPinia()).

Setup-стор и есть применение идеи композабла к глобальному состоянию. Разница в том, что обычный композабл создаёт новый экземпляр на каждый вызов, а стор Pinia возвращает один общий экземпляр на всё приложение.

Чем в setup-сторе Pinia становятся ref, computed и обычная функция?

Использование стора в компоненте

В компоненте стор получают вызовом useCartStore() внутри setup. Возвращается один и тот же экземпляр для всех компонентов - синглтон на приложение, поэтому корзина в шапке и на странице оформления это буквально одно состояние. Через сам экземпляр доступны и данные, и getters, и actions.

Ловушка возникает при деструктуризации. Стор - это reactive-объект, поэтому const { count, total } = cart разорвёт реактивность, как и любая деструктуризация reactive. Решение знакомо из урока про утилиты: storeToRefs оборачивает state и getters в ref, сохраняя связь. Actions при этом берут прямо из стора - функции стабильны и в обёртке не нуждаются.

Распространённая ошибка - деструктурировать стор как обычный объект: const { count } = useCartStore(). Значение замёрзнет на первоначальном. Для state и getters всегда нужен storeToRefs, иначе реактивность теряется молча.

Как правильно деструктурировать state и getters из стора Pinia, не потеряв реактивность?

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

Урок собирает воедино реактивность, композаблы и роутинг:

  • Композаблы — Setup-стор Pinia по форме - это композабл с особым контрактом
  • Утилиты реактивности — storeToRefs применяет toRefs к стору для безопасной деструктуризации
  • effectScope — Стор живёт в собственной области эффектов, что обеспечивает их корректную остановку

Итог

  • Pinia - официальное хранилище состояния Vue: общее реактивное состояние с понятной структурой и поддержкой devtools
  • Setup-стор объявляется через defineStore(id, setupFn), где функция возвращает state (ref/reactive), getters (computed) и actions (функции)
  • useStore вызывается внутри setup и возвращает один и тот же экземпляр стора для всех компонентов (синглтон на приложение)
  • Деструктуризация стора теряет реактивность, поэтому state и getters достают через storeToRefs, а actions берут прямо из стора
  • Pinia предпочтительнее обычного reactive-объекта: явный контракт изменений через actions, интеграция с devtools, типизация, HMR и effectScope

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

  • vue-12-composables — Setup-стор пишется как композабл, поэтому нужно понимать саму идею композаблов
  • vue-21-reactivity-utilities — storeToRefs - прямое применение toRefs для безопасной деструктуризации стора
  • vue-22-effect-scope — Каждый стор Pinia живёт внутри effectScope, что объясняет корректную остановку его эффектов
Pinia: управление состоянием

0

1

Войти