Зачем нужен Pinia и как устроен store?

MiddleVue.js #vue #pinia #state-management

Вопрос

Зачем нужно глобальное управление состоянием, как устроен store в Pinia и чем это отличается от Vuex?

Короткий ответ

Глобальное управление состоянием нужно, когда данные используются во многих несвязанных компонентах и передавать их через props/emits на несколько уровней вложенности («prop drilling») становится неудобно. Pinia — официальная библиотека состояния для Vue 3, store в ней состоит из state (данные), getters (производные значения, аналог computed) и actions (методы, включая асинхронные, для изменения state). По сравнению с Vuex, Pinia проще, не требует мутаций и модулей с пространствами имён, полностью поддерживает TypeScript «из коробки».

Подробный ответ

Зачем нужен store. Когда два компонента, далёкие друг от друга в дереве, должны читать и менять одни и те же данные (корзина покупок, данные авторизованного пользователя, тема оформления), передавать это через цепочку props вниз и events вверх становится громоздко. Store — это единый источник истины (single source of truth), реактивный объект вне дерева компонентов, к которому можно обратиться из любого места напрямую.

Структура store в Pinia (Composition-style синтаксис через defineStore):

  • state — функция, возвращающая объект с начальными данными (аналог data() у компонента). Возвращаемые значения автоматически становятся реактивными.
  • getters — производные, кэшируемые значения от state, концептуально эквивалентны computed. Могут обращаться к this (в options-синтаксисе) или получать state параметром.
  • actions — методы для изменения state, включая асинхронную логику (запросы к API). В Pinia, в отличие от Vuex, нет разделения на mutations и actions — state можно менять прямо внутри actions.

Pinia поддерживает и Setup Store синтаксис — store пишется как обычная composition-функция: ref становится state, computed — getters, обычные функции — actions. Это даёт максимальную гибкость и единообразие с остальным кодом на Composition API.

Отличия от Vuex:

VuexPinia
Изменение состояниячерез mutations (обязательно) + actionsнапрямую внутри actions
Модуливложенные, с пространствами имён (namespaced: true)плоские отдельные stores, импортируются напрямую
TypeScriptтребует дополнительной обвязки для типизациитипизация выводится автоматически
Devtoolsподдерживаетсяподдерживается, включая time-travel debugging
APIthis.$store.commit/dispatchвызов методов store напрямую, как обычных функций

Пример

// stores/cart.js
import { defineStore } from 'pinia'

export const useCartStore = defineStore('cart', {
  state: () => ({
    items: [] // [{ id, price, quantity }]
  }),
  getters: {
    totalPrice: (state) =>
      state.items.reduce((sum, item) => sum + item.price * item.quantity, 0),
    itemCount: (state) => state.items.length
  },
  actions: {
    addItem(product) {
      const existing = this.items.find((i) => i.id === product.id)
      if (existing) {
        existing.quantity++
      } else {
        this.items.push({ ...product, quantity: 1 })
      }
    },
    async checkout() {
      await fetch('/api/checkout', {
        method: 'POST',
        body: JSON.stringify(this.items)
      })
      this.items = []
    }
  }
})
<!-- CartSummary.vue -->
<script setup>
import { useCartStore } from '@/stores/cart'

const cart = useCartStore()
</script>

<template>
  <p>Товаров в корзине: {{ cart.itemCount }}</p>
  <p>Итого: {{ cart.totalPrice }} ₽</p>
  <button @click="cart.checkout()">Оформить заказ</button>
</template>

Дополнительные вопросы

  • Чем Setup Store в Pinia отличается от Options Store и когда какой выбрать?
  • Как сделать состояние store персистентным (сохранить в localStorage между перезагрузками)?
  • Почему Vuex требовал mutations для изменения state, а Pinia — нет?